Convert learner_dashboard to es2015
This commit is contained in:
committed by
Michael Terry
parent
acf7de7c02
commit
c9318c3e51
@@ -1,35 +1,23 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'text!../../../templates/learner_dashboard/certificate_list.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
certificateTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
tpl: _.template(certificateTpl),
|
||||
import _ from 'underscore';
|
||||
import Backbone from 'backbone';
|
||||
|
||||
initialize: function(options) {
|
||||
this.title = options.title || false;
|
||||
this.render();
|
||||
},
|
||||
import certificateTpl from '../../../templates/learner_dashboard/certificate_list.underscore';
|
||||
|
||||
render: function() {
|
||||
var data = {
|
||||
title: this.title,
|
||||
certificateList: this.collection.toJSON()
|
||||
};
|
||||
class CertificateListView extends Backbone.View {
|
||||
initialize(options) {
|
||||
this.tpl = _.template(certificateTpl);
|
||||
this.title = options.title || false;
|
||||
this.render();
|
||||
}
|
||||
|
||||
this.$el.html(this.tpl(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
render() {
|
||||
const data = {
|
||||
title: this.title,
|
||||
certificateList: this.collection.toJSON(),
|
||||
};
|
||||
|
||||
this.$el.html(this.tpl(data));
|
||||
}
|
||||
}
|
||||
|
||||
export default CertificateListView;
|
||||
|
||||
@@ -1,38 +1,24 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/certificate_status.underscore',
|
||||
'text!../../../templates/learner_dashboard/certificate_icon.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
certificateStatusTpl,
|
||||
certificateIconTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
statusTpl: HtmlUtils.template(certificateStatusTpl),
|
||||
iconTpl: HtmlUtils.template(certificateIconTpl),
|
||||
import Backbone from 'backbone';
|
||||
|
||||
initialize: function(options) {
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
},
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
render: function() {
|
||||
var data = this.model.toJSON();
|
||||
import certificateStatusTpl from '../../../templates/learner_dashboard/certificate_status.underscore';
|
||||
import certificateIconTpl from '../../../templates/learner_dashboard/certificate_icon.underscore';
|
||||
|
||||
data = $.extend(data, {certificateSvg: this.iconTpl()});
|
||||
HtmlUtils.setHtml(this.$el, this.statusTpl(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
class CertificateStatusView extends Backbone.View {
|
||||
initialize(options) {
|
||||
this.statusTpl = HtmlUtils.template(certificateStatusTpl);
|
||||
this.iconTpl = HtmlUtils.template(certificateIconTpl);
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
let data = this.model.toJSON();
|
||||
|
||||
data = $.extend(data, { certificateSvg: this.iconTpl() });
|
||||
HtmlUtils.setHtml(this.$el, this.statusTpl(data));
|
||||
}
|
||||
}
|
||||
|
||||
export default CertificateStatusView;
|
||||
|
||||
@@ -1,68 +1,53 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
import Backbone from 'backbone';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/string-utils',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/empty_programs_list.underscore'
|
||||
],
|
||||
function(Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
StringUtils,
|
||||
HtmlUtils,
|
||||
emptyProgramsListTpl) {
|
||||
return Backbone.View.extend({
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
import StringUtils from 'edx-ui-toolkit/js/utils/string-utils';
|
||||
|
||||
initialize: function(data) {
|
||||
this.childView = data.childView;
|
||||
this.context = data.context;
|
||||
this.titleContext = data.titleContext;
|
||||
},
|
||||
import emptyProgramsListTpl from '../../../templates/learner_dashboard/empty_programs_list.underscore';
|
||||
|
||||
render: function() {
|
||||
var childList;
|
||||
class CollectionListView extends Backbone.View {
|
||||
initialize(data) {
|
||||
this.childView = data.childView;
|
||||
this.context = data.context;
|
||||
this.titleContext = data.titleContext;
|
||||
}
|
||||
|
||||
if (!this.collection.length) {
|
||||
if (this.context.marketingUrl) {
|
||||
// Only show the advertising panel if the link is passed in
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(emptyProgramsListTpl)(this.context));
|
||||
}
|
||||
} else {
|
||||
childList = [];
|
||||
render() {
|
||||
if (!this.collection.length) {
|
||||
if (this.context.marketingUrl) {
|
||||
// Only show the advertising panel if the link is passed in
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.template(emptyProgramsListTpl)(this.context));
|
||||
}
|
||||
} else {
|
||||
const childList = [];
|
||||
|
||||
this.collection.each(function(model) {
|
||||
var child = new this.childView({
|
||||
model: model,
|
||||
context: this.context
|
||||
});
|
||||
childList.push(child.el);
|
||||
}, this);
|
||||
this.collection.each((model) => {
|
||||
const child = new this.childView({ // eslint-disable-line new-cap
|
||||
model,
|
||||
context: this.context,
|
||||
});
|
||||
childList.push(child.el);
|
||||
}, this);
|
||||
|
||||
if (this.titleContext) {
|
||||
this.$el.before(HtmlUtils.ensureHtml(this.getTitleHtml()).toString());
|
||||
}
|
||||
if (this.titleContext) {
|
||||
this.$el.before(HtmlUtils.ensureHtml(this.getTitleHtml()).toString());
|
||||
}
|
||||
|
||||
this.$el.html(childList);
|
||||
}
|
||||
},
|
||||
this.$el.html(childList);
|
||||
}
|
||||
}
|
||||
|
||||
getTitleHtml: function() {
|
||||
var titleHtml = HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<'),
|
||||
this.titleContext.el,
|
||||
HtmlUtils.HTML(' class="sr-only collection-title">'),
|
||||
StringUtils.interpolate(this.titleContext.title),
|
||||
HtmlUtils.HTML('</'),
|
||||
this.titleContext.el,
|
||||
HtmlUtils.HTML('>'));
|
||||
return titleHtml;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
getTitleHtml() {
|
||||
const titleHtml = HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<'),
|
||||
this.titleContext.el,
|
||||
HtmlUtils.HTML(' class="sr-only collection-title">'),
|
||||
StringUtils.interpolate(this.titleContext.title),
|
||||
HtmlUtils.HTML('</'),
|
||||
this.titleContext.el,
|
||||
HtmlUtils.HTML('>'));
|
||||
return titleHtml;
|
||||
}
|
||||
}
|
||||
|
||||
export default CollectionListView;
|
||||
|
||||
@@ -1,130 +1,116 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
import Backbone from 'backbone';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'js/learner_dashboard/models/course_enroll_model',
|
||||
'js/learner_dashboard/views/upgrade_message_view',
|
||||
'js/learner_dashboard/views/certificate_status_view',
|
||||
'js/learner_dashboard/views/expired_notification_view',
|
||||
'js/learner_dashboard/views/course_enroll_view',
|
||||
'js/learner_dashboard/views/course_entitlement_view',
|
||||
'text!../../../templates/learner_dashboard/course_card.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
EnrollModel,
|
||||
UpgradeMessageView,
|
||||
CertificateStatusView,
|
||||
ExpiredNotificationView,
|
||||
CourseEnrollView,
|
||||
EntitlementView,
|
||||
pageTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
className: 'program-course-card',
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
import EnrollModel from '../models/course_enroll_model';
|
||||
import UpgradeMessageView from './upgrade_message_view';
|
||||
import CertificateStatusView from './certificate_status_view';
|
||||
import ExpiredNotificationView from './expired_notification_view';
|
||||
import CourseEnrollView from './course_enroll_view';
|
||||
import EntitlementView from './course_entitlement_view';
|
||||
|
||||
initialize: function(options) {
|
||||
this.enrollModel = new EnrollModel();
|
||||
if (options.context) {
|
||||
this.urlModel = new Backbone.Model(options.context.urls);
|
||||
this.enrollModel.urlRoot = this.urlModel.get('commerce_api_url');
|
||||
}
|
||||
this.context = options.context || {};
|
||||
this.grade = this.context.courseData.grades[this.model.get('course_run_key')];
|
||||
this.grade = this.grade * 100;
|
||||
this.collectionCourseStatus = this.context.collectionCourseStatus || '';
|
||||
this.entitlement = this.model.get('user_entitlement');
|
||||
import pageTpl from '../../../templates/learner_dashboard/course_card.underscore';
|
||||
|
||||
this.render();
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
class CourseCardView extends Backbone.View {
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
className: 'program-course-card',
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
render: function() {
|
||||
var data = $.extend(this.model.toJSON(), {
|
||||
enrolled: this.context.enrolled || ''
|
||||
});
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
},
|
||||
initialize(options) {
|
||||
this.tpl = HtmlUtils.template(pageTpl);
|
||||
this.enrollModel = new EnrollModel();
|
||||
if (options.context) {
|
||||
this.urlModel = new Backbone.Model(options.context.urls);
|
||||
this.enrollModel.urlRoot = this.urlModel.get('commerce_api_url');
|
||||
}
|
||||
this.context = options.context || {};
|
||||
this.grade = this.context.courseData.grades[this.model.get('course_run_key')];
|
||||
this.grade = this.grade * 100;
|
||||
this.collectionCourseStatus = this.context.collectionCourseStatus || '';
|
||||
this.entitlement = this.model.get('user_entitlement');
|
||||
|
||||
postRender: function() {
|
||||
var $upgradeMessage = this.$('.upgrade-message'),
|
||||
$certStatus = this.$('.certificate-status'),
|
||||
$expiredNotification = this.$('.expired-notification'),
|
||||
expired = this.model.get('expired'),
|
||||
courseUUID = this.model.get('uuid'),
|
||||
containerSelector = '#course-' + courseUUID;
|
||||
this.render();
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
}
|
||||
|
||||
this.enrollView = new CourseEnrollView({
|
||||
$parentEl: this.$('.course-actions'),
|
||||
model: this.model,
|
||||
grade: this.grade,
|
||||
collectionCourseStatus: this.collectionCourseStatus,
|
||||
urlModel: this.urlModel,
|
||||
enrollModel: this.enrollModel
|
||||
});
|
||||
render() {
|
||||
const data = $.extend(this.model.toJSON(), {
|
||||
enrolled: this.context.enrolled || '',
|
||||
});
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
}
|
||||
|
||||
if (this.entitlement) {
|
||||
this.sessionSelectionView = new EntitlementView({
|
||||
el: this.$(containerSelector + ' .course-entitlement-selection-container'),
|
||||
$parentEl: this.$el,
|
||||
courseCardModel: this.model,
|
||||
enrollModel: this.enrollModel,
|
||||
triggerOpenBtn: '.course-details .change-session',
|
||||
courseCardMessages: '',
|
||||
courseImageLink: '',
|
||||
courseTitleLink: containerSelector + ' .course-details .course-title',
|
||||
dateDisplayField: containerSelector + ' .course-details .course-text',
|
||||
enterCourseBtn: containerSelector + ' .view-course-button',
|
||||
availableSessions: JSON.stringify(this.model.get('course_runs')),
|
||||
entitlementUUID: this.entitlement.uuid,
|
||||
currentSessionId: this.model.isEnrolledInSession() ?
|
||||
postRender() {
|
||||
const $upgradeMessage = this.$('.upgrade-message');
|
||||
const $certStatus = this.$('.certificate-status');
|
||||
const $expiredNotification = this.$('.expired-notification');
|
||||
const expired = this.model.get('expired');
|
||||
const courseUUID = this.model.get('uuid');
|
||||
const containerSelector = `#course-${courseUUID}`;
|
||||
|
||||
this.enrollView = new CourseEnrollView({
|
||||
$parentEl: this.$('.course-actions'),
|
||||
model: this.model,
|
||||
grade: this.grade,
|
||||
collectionCourseStatus: this.collectionCourseStatus,
|
||||
urlModel: this.urlModel,
|
||||
enrollModel: this.enrollModel,
|
||||
});
|
||||
|
||||
if (this.entitlement) {
|
||||
this.sessionSelectionView = new EntitlementView({
|
||||
el: this.$(`${containerSelector} .course-entitlement-selection-container`),
|
||||
$parentEl: this.$el,
|
||||
courseCardModel: this.model,
|
||||
enrollModel: this.enrollModel,
|
||||
triggerOpenBtn: '.course-details .change-session',
|
||||
courseCardMessages: '',
|
||||
courseImageLink: '',
|
||||
courseTitleLink: `${containerSelector} .course-details .course-title`,
|
||||
dateDisplayField: `${containerSelector} .course-details .course-text`,
|
||||
enterCourseBtn: `${containerSelector} .view-course-button`,
|
||||
availableSessions: JSON.stringify(this.model.get('course_runs')),
|
||||
entitlementUUID: this.entitlement.uuid,
|
||||
currentSessionId: this.model.isEnrolledInSession() ?
|
||||
this.model.get('course_run_key') : null,
|
||||
enrollUrl: this.model.get('enroll_url'),
|
||||
courseHomeUrl: this.model.get('course_url'),
|
||||
expiredAt: this.entitlement.expired_at,
|
||||
daysUntilExpiration: this.entitlement.days_until_expiration
|
||||
});
|
||||
}
|
||||
enrollUrl: this.model.get('enroll_url'),
|
||||
courseHomeUrl: this.model.get('course_url'),
|
||||
expiredAt: this.entitlement.expired_at,
|
||||
daysUntilExpiration: this.entitlement.days_until_expiration,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.model.get('upgrade_url') && !(expired === true)) {
|
||||
this.upgradeMessage = new UpgradeMessageView({
|
||||
$el: $upgradeMessage,
|
||||
model: this.model
|
||||
});
|
||||
if (this.model.get('upgrade_url') && !(expired === true)) {
|
||||
this.upgradeMessage = new UpgradeMessageView({
|
||||
$el: $upgradeMessage,
|
||||
model: this.model,
|
||||
});
|
||||
|
||||
$certStatus.remove();
|
||||
} else if (this.model.get('certificate_url') && !(expired === true)) {
|
||||
this.certificateStatus = new CertificateStatusView({
|
||||
$el: $certStatus,
|
||||
model: this.model
|
||||
});
|
||||
$certStatus.remove();
|
||||
} else if (this.model.get('certificate_url') && !(expired === true)) {
|
||||
this.certificateStatus = new CertificateStatusView({
|
||||
$el: $certStatus,
|
||||
model: this.model,
|
||||
});
|
||||
|
||||
$upgradeMessage.remove();
|
||||
} else {
|
||||
// Styles are applied to these elements which will be visible if they're empty.
|
||||
$upgradeMessage.remove();
|
||||
$certStatus.remove();
|
||||
}
|
||||
$upgradeMessage.remove();
|
||||
} else {
|
||||
// Styles are applied to these elements which will be visible if they're empty.
|
||||
$upgradeMessage.remove();
|
||||
$certStatus.remove();
|
||||
}
|
||||
|
||||
if (expired) {
|
||||
this.expiredNotification = new ExpiredNotificationView({
|
||||
$el: $expiredNotification,
|
||||
model: this.model
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
if (expired) {
|
||||
this.expiredNotification = new ExpiredNotificationView({
|
||||
$el: $expiredNotification,
|
||||
model: this.model,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CourseCardView;
|
||||
|
||||
@@ -1,120 +1,111 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
import _ from 'underscore';
|
||||
import Backbone from 'backbone';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/course_enroll.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
pageTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
className: 'course-enroll-view',
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
import pageTpl from '../../../templates/learner_dashboard/course_enroll.underscore';
|
||||
|
||||
events: {
|
||||
'click .enroll-button': 'handleEnroll',
|
||||
'change .run-select': 'updateEnrollUrl'
|
||||
},
|
||||
class CourseEnrollView extends Backbone.View {
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
className: 'course-enroll-view',
|
||||
events: {
|
||||
'click .enroll-button': 'handleEnroll',
|
||||
'change .run-select': 'updateEnrollUrl',
|
||||
},
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
initialize: function(options) {
|
||||
this.$parentEl = options.$parentEl;
|
||||
this.enrollModel = options.enrollModel;
|
||||
this.urlModel = options.urlModel;
|
||||
this.grade = options.grade;
|
||||
this.collectionCourseStatus = options.collectionCourseStatus;
|
||||
this.render();
|
||||
},
|
||||
initialize(options) {
|
||||
this.tpl = HtmlUtils.template(pageTpl);
|
||||
this.$parentEl = options.$parentEl;
|
||||
this.enrollModel = options.enrollModel;
|
||||
this.urlModel = options.urlModel;
|
||||
this.grade = options.grade;
|
||||
this.collectionCourseStatus = options.collectionCourseStatus;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render: function() {
|
||||
var filledTemplate,
|
||||
context = this.model.toJSON();
|
||||
if (this.$parentEl && this.enrollModel) {
|
||||
context.grade = this.grade;
|
||||
context.collectionCourseStatus = this.collectionCourseStatus;
|
||||
filledTemplate = this.tpl(context);
|
||||
HtmlUtils.setHtml(this.$el, filledTemplate);
|
||||
HtmlUtils.setHtml(this.$parentEl, HtmlUtils.HTML(this.$el));
|
||||
}
|
||||
this.postRender();
|
||||
},
|
||||
render() {
|
||||
let filledTemplate;
|
||||
const context = this.model.toJSON();
|
||||
if (this.$parentEl && this.enrollModel) {
|
||||
context.grade = this.grade;
|
||||
context.collectionCourseStatus = this.collectionCourseStatus;
|
||||
filledTemplate = this.tpl(context);
|
||||
HtmlUtils.setHtml(this.$el, filledTemplate);
|
||||
HtmlUtils.setHtml(this.$parentEl, HtmlUtils.HTML(this.$el));
|
||||
}
|
||||
this.postRender();
|
||||
}
|
||||
|
||||
postRender: function() {
|
||||
if (this.urlModel) {
|
||||
this.trackSelectionUrl = this.urlModel.get('track_selection_url');
|
||||
}
|
||||
},
|
||||
postRender() {
|
||||
if (this.urlModel) {
|
||||
this.trackSelectionUrl = this.urlModel.get('track_selection_url');
|
||||
}
|
||||
}
|
||||
|
||||
handleEnroll: function() {
|
||||
// Enrollment click event handled here
|
||||
if (this.model.get('is_mobile_only') !== true) {
|
||||
var courseRunKey = $('.run-select').val() || this.model.get('course_run_key'); // eslint-disable-line vars-on-top, max-len
|
||||
this.model.updateCourseRun(courseRunKey);
|
||||
if (this.model.get('is_enrolled')) {
|
||||
// Create the enrollment.
|
||||
this.enrollModel.save({
|
||||
course_id: courseRunKey
|
||||
}, {
|
||||
success: _.bind(this.enrollSuccess, this),
|
||||
error: _.bind(this.enrollError, this)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
handleEnroll() {
|
||||
// Enrollment click event handled here
|
||||
if (this.model.get('is_mobile_only') !== true) {
|
||||
const courseRunKey = $('.run-select').val() || this.model.get('course_run_key');
|
||||
this.model.updateCourseRun(courseRunKey);
|
||||
if (this.model.get('is_enrolled')) {
|
||||
// Create the enrollment.
|
||||
this.enrollModel.save({
|
||||
course_id: courseRunKey,
|
||||
}, {
|
||||
success: _.bind(this.enrollSuccess, this),
|
||||
error: _.bind(this.enrollError, this),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enrollSuccess: function() {
|
||||
var courseRunKey = this.model.get('course_run_key');
|
||||
window.analytics.track('edx.bi.user.program-details.enrollment');
|
||||
if (this.trackSelectionUrl) {
|
||||
// Go to track selection page
|
||||
this.redirect(this.trackSelectionUrl + courseRunKey);
|
||||
} else {
|
||||
this.model.set({
|
||||
is_enrolled: true
|
||||
});
|
||||
}
|
||||
},
|
||||
enrollSuccess() {
|
||||
const courseRunKey = this.model.get('course_run_key');
|
||||
window.analytics.track('edx.bi.user.program-details.enrollment');
|
||||
if (this.trackSelectionUrl) {
|
||||
// Go to track selection page
|
||||
CourseEnrollView.redirect(this.trackSelectionUrl + courseRunKey);
|
||||
} else {
|
||||
this.model.set({
|
||||
is_enrolled: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enrollError: function(model, response) {
|
||||
if (response.status === 403 && response.responseJSON.user_message_url) {
|
||||
/**
|
||||
* Check if we've been blocked from the course
|
||||
* because of country access rules.
|
||||
* If so, redirect to a page explaining to the user
|
||||
* why they were blocked.
|
||||
*/
|
||||
this.redirect(response.responseJSON.user_message_url);
|
||||
} else if (this.trackSelectionUrl) {
|
||||
/**
|
||||
* Otherwise, go to the track selection page as usual.
|
||||
* This can occur, for example, when a course does not
|
||||
* have a free enrollment mode, so we can't auto-enroll.
|
||||
*/
|
||||
this.redirect(this.trackSelectionUrl + this.model.get('course_run_key'));
|
||||
}
|
||||
},
|
||||
enrollError(model, response) {
|
||||
if (response.status === 403 && response.responseJSON.user_message_url) {
|
||||
/**
|
||||
* Check if we've been blocked from the course
|
||||
* because of country access rules.
|
||||
* If so, redirect to a page explaining to the user
|
||||
* why they were blocked.
|
||||
*/
|
||||
CourseEnrollView.redirect(response.responseJSON.user_message_url);
|
||||
} else if (this.trackSelectionUrl) {
|
||||
/**
|
||||
* Otherwise, go to the track selection page as usual.
|
||||
* This can occur, for example, when a course does not
|
||||
* have a free enrollment mode, so we can't auto-enroll.
|
||||
*/
|
||||
CourseEnrollView.redirect(this.trackSelectionUrl + this.model.get('course_run_key'));
|
||||
}
|
||||
}
|
||||
|
||||
updateEnrollUrl: function() {
|
||||
if (this.model.get('is_mobile_only') === true) {
|
||||
var courseRunKey = $('.run-select').val(), // eslint-disable-line vars-on-top
|
||||
href = 'edxapp://enroll?course_id=' + courseRunKey + '&email_opt_in=true';
|
||||
$('.enroll-course-button').attr('href', href);
|
||||
}
|
||||
},
|
||||
updateEnrollUrl() {
|
||||
if (this.model.get('is_mobile_only') === true) {
|
||||
const courseRunKey = $('.run-select').val();
|
||||
const href = `edxapp://enroll?course_id=${courseRunKey}&email_opt_in=true`;
|
||||
$('.enroll-course-button').attr('href', href);
|
||||
}
|
||||
}
|
||||
|
||||
redirect: function(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
static redirect(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
export default CourseEnrollView;
|
||||
|
||||
@@ -1,419 +1,409 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
/* globals gettext */
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'moment',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'js/learner_dashboard/models/course_entitlement_model',
|
||||
'js/learner_dashboard/models/course_card_model',
|
||||
'text!../../../templates/learner_dashboard/course_entitlement.underscore',
|
||||
'text!../../../templates/learner_dashboard/verification_popover.underscore',
|
||||
'bootstrap'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
moment,
|
||||
HtmlUtils,
|
||||
EntitlementModel,
|
||||
CourseCardModel,
|
||||
pageTpl,
|
||||
verificationPopoverTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
verificationTpl: HtmlUtils.template(verificationPopoverTpl),
|
||||
import 'bootstrap';
|
||||
|
||||
events: {
|
||||
'change .session-select': 'updateEnrollBtn',
|
||||
'click .enroll-btn': 'handleEnrollChange',
|
||||
'keydown .final-confirmation-btn': 'handleVerificationPopoverA11y',
|
||||
'click .popover-dismiss': 'hideDialog'
|
||||
},
|
||||
import _ from 'underscore';
|
||||
import Backbone from 'backbone';
|
||||
import moment from 'moment';
|
||||
|
||||
initialize: function(options) {
|
||||
// Set up models and reload view on change
|
||||
this.courseCardModel = options.courseCardModel || new CourseCardModel();
|
||||
this.enrollModel = options.enrollModel;
|
||||
this.entitlementModel = new EntitlementModel({
|
||||
availableSessions: this.formatDates(JSON.parse(options.availableSessions)),
|
||||
entitlementUUID: options.entitlementUUID,
|
||||
currentSessionId: options.currentSessionId,
|
||||
expiredAt: options.expiredAt,
|
||||
expiresAtDate: this.courseCardModel.formatDate(
|
||||
new moment().utc().add(options.daysUntilExpiration, 'days')
|
||||
),
|
||||
courseName: options.courseName
|
||||
});
|
||||
this.listenTo(this.entitlementModel, 'change', this.render);
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
// Grab URLs that handle changing of enrollment and entering a newly selected session.
|
||||
this.enrollUrl = options.enrollUrl;
|
||||
this.courseHomeUrl = options.courseHomeUrl;
|
||||
import EntitlementModel from '../models/course_entitlement_model';
|
||||
import CourseCardModel from '../models/course_card_model';
|
||||
|
||||
// Grab elements from the parent card that work with this view
|
||||
this.$parentEl = options.$parentEl; // Containing course card (must be a backbone view root el)
|
||||
this.$enterCourseBtn = $(options.enterCourseBtn); // Button link to course home page
|
||||
this.$courseCardMessages = $(options.courseCardMessages); // Additional session messages
|
||||
this.$courseTitleLink = $(options.courseTitleLink); // Title link to course home page
|
||||
this.$courseImageLink = $(options.courseImageLink); // Image link to course home page
|
||||
this.$policyMsg = $(options.policyMsg); // Message for policy information
|
||||
import pageTpl from '../../../templates/learner_dashboard/course_entitlement.underscore';
|
||||
import verificationPopoverTpl from '../../../templates/learner_dashboard/verification_popover.underscore';
|
||||
|
||||
// Bind action elements with associated events to objects outside this view
|
||||
this.$dateDisplayField = this.$parentEl ? this.$parentEl.find(options.dateDisplayField) :
|
||||
$(options.dateDisplayField); // Displays current session dates
|
||||
this.$triggerOpenBtn = this.$parentEl ? this.$parentEl.find(options.triggerOpenBtn) :
|
||||
$(options.triggerOpenBtn); // Opens/closes session selection view
|
||||
this.$triggerOpenBtn.on('click', this.toggleSessionSelectionPanel.bind(this));
|
||||
class CourseEntitlementView extends Backbone.View {
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
events: {
|
||||
'change .session-select': 'updateEnrollBtn',
|
||||
'click .enroll-btn': 'handleEnrollChange',
|
||||
'keydown .final-confirmation-btn': 'handleVerificationPopoverA11y',
|
||||
'click .popover-dismiss': 'hideDialog',
|
||||
},
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
this.render(options);
|
||||
this.postRender();
|
||||
},
|
||||
initialize(options) {
|
||||
this.tpl = HtmlUtils.template(pageTpl);
|
||||
this.verificationTpl = HtmlUtils.template(verificationPopoverTpl);
|
||||
|
||||
render: function() {
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(this.entitlementModel.toJSON()));
|
||||
this.delegateEvents();
|
||||
this.updateEnrollBtn();
|
||||
return this;
|
||||
},
|
||||
// Set up models and reload view on change
|
||||
this.courseCardModel = options.courseCardModel || new CourseCardModel();
|
||||
this.enrollModel = options.enrollModel;
|
||||
this.entitlementModel = new EntitlementModel({
|
||||
availableSessions: this.formatDates(JSON.parse(options.availableSessions)),
|
||||
entitlementUUID: options.entitlementUUID,
|
||||
currentSessionId: options.currentSessionId,
|
||||
expiredAt: options.expiredAt,
|
||||
expiresAtDate: CourseCardModel.formatDate(
|
||||
new moment().utc().add(options.daysUntilExpiration, 'days'), // eslint-disable-line new-cap
|
||||
),
|
||||
courseName: options.courseName,
|
||||
});
|
||||
this.listenTo(this.entitlementModel, 'change', this.render);
|
||||
|
||||
postRender: function() {
|
||||
// Close any visible popovers on click-away
|
||||
$(document).on('click', function(e) {
|
||||
if (this.$('.popover:visible').length &&
|
||||
!($(e.target).closest('.enroll-btn-initial, .popover').length)) {
|
||||
this.hideDialog(this.$('.enroll-btn-initial'));
|
||||
}
|
||||
}.bind(this));
|
||||
// Grab URLs that handle changing of enrollment and entering a newly selected session.
|
||||
this.enrollUrl = options.enrollUrl;
|
||||
this.courseHomeUrl = options.courseHomeUrl;
|
||||
|
||||
// Initialize focus to cancel button on popover load
|
||||
$(document).on('shown.bs.popover', function() {
|
||||
this.$('.final-confirmation-btn:first').focus();
|
||||
}.bind(this));
|
||||
},
|
||||
// Grab elements from the parent card that work with this view
|
||||
this.$parentEl = options.$parentEl; // Containing course card (must be a backbone view root el)
|
||||
this.$enterCourseBtn = $(options.enterCourseBtn); // Button link to course home page
|
||||
this.$courseCardMessages = $(options.courseCardMessages); // Additional session messages
|
||||
this.$courseTitleLink = $(options.courseTitleLink); // Title link to course home page
|
||||
this.$courseImageLink = $(options.courseImageLink); // Image link to course home page
|
||||
this.$policyMsg = $(options.policyMsg); // Message for policy information
|
||||
|
||||
handleEnrollChange: function() {
|
||||
/*
|
||||
Handles enrolling in a course, unenrolling in a session and changing session.
|
||||
The new session id is stored as a data attribute on the option in the session-select element.
|
||||
*/
|
||||
var isLeavingSession;
|
||||
// Bind action elements with associated events to objects outside this view
|
||||
this.$dateDisplayField = this.$parentEl ? this.$parentEl.find(options.dateDisplayField) :
|
||||
$(options.dateDisplayField); // Displays current session dates
|
||||
this.$triggerOpenBtn = this.$parentEl ? this.$parentEl.find(options.triggerOpenBtn) :
|
||||
$(options.triggerOpenBtn); // Opens/closes session selection view
|
||||
this.$triggerOpenBtn.on('click', this.toggleSessionSelectionPanel.bind(this));
|
||||
|
||||
// Do not allow for enrollment when button is disabled
|
||||
if (this.$('.enroll-btn-initial').hasClass('disabled')) return;
|
||||
this.render(options);
|
||||
this.postRender();
|
||||
}
|
||||
|
||||
// Grab the id for the desired session, an leave session event will return null
|
||||
this.currentSessionSelection = this.$('.session-select')
|
||||
.find('option:selected').data('session_id');
|
||||
isLeavingSession = !this.currentSessionSelection;
|
||||
render() {
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(this.entitlementModel.toJSON()));
|
||||
this.delegateEvents();
|
||||
this.updateEnrollBtn();
|
||||
return this;
|
||||
}
|
||||
|
||||
// Display the indicator icon
|
||||
HtmlUtils.setHtml(this.$dateDisplayField,
|
||||
HtmlUtils.HTML('<span class="fa fa-spinner fa-spin" aria-hidden="true"></span>')
|
||||
);
|
||||
postRender() {
|
||||
// Close any visible popovers on click-away
|
||||
$(document).on('click', (e) => {
|
||||
if (this.$('.popover:visible').length &&
|
||||
!($(e.target).closest('.enroll-btn-initial, .popover').length)) {
|
||||
this.hideDialog(this.$('.enroll-btn-initial'));
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
type: isLeavingSession ? 'DELETE' : 'POST',
|
||||
url: this.enrollUrl,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({
|
||||
course_run_id: this.currentSessionSelection
|
||||
}),
|
||||
statusCode: {
|
||||
201: _.bind(this.enrollSuccess, this),
|
||||
204: _.bind(this.unenrollSuccess, this)
|
||||
},
|
||||
error: _.bind(this.enrollError, this)
|
||||
});
|
||||
},
|
||||
// Initialize focus to cancel button on popover load
|
||||
$(document).on('shown.bs.popover', () => {
|
||||
this.$('.final-confirmation-btn:first').focus();
|
||||
});
|
||||
}
|
||||
|
||||
enrollSuccess: function(data) {
|
||||
/*
|
||||
Update external elements on the course card to represent the now available course session.
|
||||
handleEnrollChange() {
|
||||
/*
|
||||
Handles enrolling in a course, unenrolling in a session and changing session.
|
||||
The new session id is stored as a data attribute on the option in the session-select element.
|
||||
*/
|
||||
// Do not allow for enrollment when button is disabled
|
||||
if (this.$('.enroll-btn-initial').hasClass('disabled')) return;
|
||||
|
||||
1) Show the change session toggle button.
|
||||
2) Add the new session's dates to the date field on the main course card.
|
||||
3) Hide the 'View Course' button to the course card.
|
||||
*/
|
||||
var successIconEl = '<span class="fa fa-check" aria-hidden="true"></span>';
|
||||
// Grab the id for the desired session, an leave session event will return null
|
||||
this.currentSessionSelection = this.$('.session-select')
|
||||
.find('option:selected').data('session_id');
|
||||
const isLeavingSession = !this.currentSessionSelection;
|
||||
|
||||
// With a containing backbone view, we can simply re-render the parent card
|
||||
if (this.$parentEl) {
|
||||
this.courseCardModel.updateCourseRun(this.currentSessionSelection);
|
||||
return;
|
||||
}
|
||||
// Display the indicator icon
|
||||
HtmlUtils.setHtml(this.$dateDisplayField,
|
||||
HtmlUtils.HTML('<span class="fa fa-spinner fa-spin" aria-hidden="true"></span>'),
|
||||
);
|
||||
|
||||
// Update the model with the new session Id
|
||||
this.entitlementModel.set({currentSessionId: this.currentSessionSelection});
|
||||
$.ajax({
|
||||
type: isLeavingSession ? 'DELETE' : 'POST',
|
||||
url: this.enrollUrl,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({
|
||||
course_run_id: this.currentSessionSelection,
|
||||
}),
|
||||
statusCode: {
|
||||
201: _.bind(this.enrollSuccess, this),
|
||||
204: _.bind(this.unenrollSuccess, this),
|
||||
},
|
||||
error: _.bind(this.enrollError, this),
|
||||
});
|
||||
}
|
||||
|
||||
// Allow user to change session
|
||||
this.$triggerOpenBtn.removeClass('hidden');
|
||||
enrollSuccess(data) {
|
||||
/*
|
||||
Update external elements on the course card to represent the now available course session.
|
||||
|
||||
// Display a success indicator
|
||||
HtmlUtils.setHtml(this.$dateDisplayField,
|
||||
HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML(successIconEl),
|
||||
this.getAvailableSessionWithId(data.course_run_id).session_dates
|
||||
)
|
||||
);
|
||||
1) Show the change session toggle button.
|
||||
2) Add the new session's dates to the date field on the main course card.
|
||||
3) Hide the 'View Course' button to the course card.
|
||||
*/
|
||||
const successIconEl = '<span class="fa fa-check" aria-hidden="true"></span>';
|
||||
|
||||
// Ensure the view course button links to new session home page and place focus there
|
||||
this.$enterCourseBtn
|
||||
.attr('href', this.formatCourseHomeUrl(data.course_run_id))
|
||||
.removeClass('hidden')
|
||||
.focus();
|
||||
this.toggleSessionSelectionPanel();
|
||||
},
|
||||
// With a containing backbone view, we can simply re-render the parent card
|
||||
if (this.$parentEl) {
|
||||
this.courseCardModel.updateCourseRun(this.currentSessionSelection);
|
||||
return;
|
||||
}
|
||||
|
||||
unenrollSuccess: function() {
|
||||
/*
|
||||
Update external elements on the course card to represent the unenrolled state.
|
||||
// Update the model with the new session Id
|
||||
this.entitlementModel.set({ currentSessionId: this.currentSessionSelection });
|
||||
|
||||
1) Hide the change session button and the date field.
|
||||
2) Hide the 'View Course' button.
|
||||
3) Remove the messages associated with the enrolled state.
|
||||
4) Remove the link from the course card image and title.
|
||||
*/
|
||||
// With a containing backbone view, we can simply re-render the parent card
|
||||
if (this.$parentEl) {
|
||||
this.courseCardModel.setUnselected();
|
||||
return;
|
||||
}
|
||||
// Allow user to change session
|
||||
this.$triggerOpenBtn.removeClass('hidden');
|
||||
|
||||
// Update the model with the new session Id;
|
||||
this.entitlementModel.set({currentSessionId: this.currentSessionSelection});
|
||||
// Display a success indicator
|
||||
HtmlUtils.setHtml(this.$dateDisplayField,
|
||||
HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML(successIconEl),
|
||||
this.getAvailableSessionWithId(data.course_run_id).session_dates,
|
||||
),
|
||||
);
|
||||
|
||||
// Reset the card contents to the unenrolled state
|
||||
this.$triggerOpenBtn.addClass('hidden');
|
||||
this.$enterCourseBtn.addClass('hidden');
|
||||
// Remove all message except for related programs, which should always be shown
|
||||
// (Even other messages might need to be shown again in future: LEARNER-3523.)
|
||||
this.$courseCardMessages.filter(':not(.message-related-programs)').remove();
|
||||
this.$policyMsg.remove();
|
||||
this.$('.enroll-btn-initial').focus();
|
||||
HtmlUtils.setHtml(
|
||||
this.$dateDisplayField,
|
||||
HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<span class="icon fa fa-warning" aria-hidden="true"></span>'),
|
||||
HtmlUtils.HTML(gettext('You must select a session to access the course.'))
|
||||
)
|
||||
);
|
||||
// Ensure the view course button links to new session home page and place focus there
|
||||
this.$enterCourseBtn
|
||||
.attr('href', this.formatCourseHomeUrl(data.course_run_id))
|
||||
.removeClass('hidden')
|
||||
.focus();
|
||||
this.toggleSessionSelectionPanel();
|
||||
}
|
||||
|
||||
// Remove links to previously enrolled sessions
|
||||
this.$courseImageLink.replaceWith( // xss-lint: disable=javascript-jquery-insertion
|
||||
HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<div class="'),
|
||||
this.$courseImageLink.attr('class'),
|
||||
HtmlUtils.HTML('" tabindex="-1">'),
|
||||
HtmlUtils.HTML(this.$courseImageLink.html()),
|
||||
HtmlUtils.HTML('</div>')
|
||||
).text
|
||||
);
|
||||
this.$courseTitleLink.replaceWith( // xss-lint: disable=javascript-jquery-insertion
|
||||
HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<span>'),
|
||||
this.$courseTitleLink.text(),
|
||||
HtmlUtils.HTML('</span>')
|
||||
).text
|
||||
);
|
||||
},
|
||||
unenrollSuccess() {
|
||||
/*
|
||||
Update external elements on the course card to represent the unenrolled state.
|
||||
|
||||
enrollError: function() {
|
||||
// Display a success indicator
|
||||
var errorMsgEl = HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<span class="enroll-error">'),
|
||||
gettext('There was an error. Please reload the page and try again.'),
|
||||
HtmlUtils.HTML('</spandiv>')
|
||||
).text;
|
||||
1) Hide the change session button and the date field.
|
||||
2) Hide the 'View Course' button.
|
||||
3) Remove the messages associated with the enrolled state.
|
||||
4) Remove the link from the course card image and title.
|
||||
*/
|
||||
// With a containing backbone view, we can simply re-render the parent card
|
||||
if (this.$parentEl) {
|
||||
this.courseCardModel.setUnselected();
|
||||
return;
|
||||
}
|
||||
|
||||
this.$dateDisplayField
|
||||
.find('.fa.fa-spin')
|
||||
.removeClass('fa-spin fa-spinner')
|
||||
.addClass('fa-close');
|
||||
// Update the model with the new session Id;
|
||||
this.entitlementModel.set({ currentSessionId: this.currentSessionSelection });
|
||||
|
||||
this.$dateDisplayField.append(errorMsgEl);
|
||||
this.hideDialog(this.$('.enroll-btn-initial'));
|
||||
},
|
||||
// Reset the card contents to the unenrolled state
|
||||
this.$triggerOpenBtn.addClass('hidden');
|
||||
this.$enterCourseBtn.addClass('hidden');
|
||||
// Remove all message except for related programs, which should always be shown
|
||||
// (Even other messages might need to be shown again in future: LEARNER-3523.)
|
||||
this.$courseCardMessages.filter(':not(.message-related-programs)').remove();
|
||||
this.$policyMsg.remove();
|
||||
this.$('.enroll-btn-initial').focus();
|
||||
HtmlUtils.setHtml(
|
||||
this.$dateDisplayField,
|
||||
HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<span class="icon fa fa-warning" aria-hidden="true"></span>'),
|
||||
HtmlUtils.HTML(gettext('You must select a session to access the course.')),
|
||||
),
|
||||
);
|
||||
|
||||
updateEnrollBtn: function() {
|
||||
/*
|
||||
This function is invoked on load, on opening the view and on changing the option on the session
|
||||
selection dropdown. It plays three roles:
|
||||
1) Enables and disables enroll button
|
||||
2) Changes text to describe the action taken
|
||||
3) Formats the confirmation popover to allow for two step authentication
|
||||
*/
|
||||
var enrollText,
|
||||
currentSessionId = this.entitlementModel.get('currentSessionId'),
|
||||
newSessionId = this.$('.session-select').find('option:selected').data('session_id'),
|
||||
enrollBtnInitial = this.$('.enroll-btn-initial');
|
||||
// Remove links to previously enrolled sessions
|
||||
this.$courseImageLink.replaceWith( // xss-lint: disable=javascript-jquery-insertion
|
||||
HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<div class="'),
|
||||
this.$courseImageLink.attr('class'),
|
||||
HtmlUtils.HTML('" tabindex="-1">'),
|
||||
HtmlUtils.HTML(this.$courseImageLink.html()),
|
||||
HtmlUtils.HTML('</div>'),
|
||||
).text,
|
||||
);
|
||||
this.$courseTitleLink.replaceWith( // xss-lint: disable=javascript-jquery-insertion
|
||||
HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<span>'),
|
||||
this.$courseTitleLink.text(),
|
||||
HtmlUtils.HTML('</span>'),
|
||||
).text,
|
||||
);
|
||||
}
|
||||
|
||||
// Disable the button if the user is already enrolled in that session.
|
||||
if (currentSessionId === newSessionId) {
|
||||
enrollBtnInitial.addClass('disabled');
|
||||
this.removeDialog(enrollBtnInitial);
|
||||
return;
|
||||
}
|
||||
enrollBtnInitial.removeClass('disabled');
|
||||
enrollError() {
|
||||
// Display a success indicator
|
||||
const errorMsgEl = HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<span class="enroll-error">'),
|
||||
gettext('There was an error. Please reload the page and try again.'),
|
||||
HtmlUtils.HTML('</spandiv>'),
|
||||
).text;
|
||||
|
||||
// Update button text specifying if the user is initially enrolling, changing or leaving a session.
|
||||
if (newSessionId) {
|
||||
enrollText = currentSessionId ? gettext('Change Session') : gettext('Select Session');
|
||||
} else {
|
||||
enrollText = gettext('Leave Current Session');
|
||||
}
|
||||
enrollBtnInitial.text(enrollText);
|
||||
this.initializeVerificationDialog(enrollBtnInitial);
|
||||
},
|
||||
this.$dateDisplayField
|
||||
.find('.fa.fa-spin')
|
||||
.removeClass('fa-spin fa-spinner')
|
||||
.addClass('fa-close');
|
||||
|
||||
toggleSessionSelectionPanel: function() {
|
||||
/*
|
||||
Opens and closes the session selection panel.
|
||||
*/
|
||||
this.$el.toggleClass('hidden');
|
||||
if (!this.$el.hasClass('hidden')) {
|
||||
// Set focus to the session selection for a11y purposes
|
||||
this.$('.session-select').focus();
|
||||
this.hideDialog(this.$('.enroll-btn-initial'));
|
||||
}
|
||||
this.updateEnrollBtn();
|
||||
},
|
||||
this.$dateDisplayField.append(errorMsgEl);
|
||||
this.hideDialog(this.$('.enroll-btn-initial'));
|
||||
}
|
||||
|
||||
initializeVerificationDialog: function(invokingElement) {
|
||||
/*
|
||||
Instantiates an instance of the Bootstrap v4 dialog modal and attaches it to the passed in element.
|
||||
updateEnrollBtn() {
|
||||
/*
|
||||
This function is invoked on load, on opening the view and on changing the option on the session
|
||||
selection dropdown. It plays three roles:
|
||||
1) Enables and disables enroll button
|
||||
2) Changes text to describe the action taken
|
||||
3) Formats the confirmation popover to allow for two step authentication
|
||||
*/
|
||||
let enrollText;
|
||||
const currentSessionId = this.entitlementModel.get('currentSessionId');
|
||||
const newSessionId = this.$('.session-select').find('option:selected').data('session_id');
|
||||
const enrollBtnInitial = this.$('.enroll-btn-initial');
|
||||
|
||||
This dialog acts as the second step in verifying the user's action to select, change or leave an
|
||||
available course session.
|
||||
*/
|
||||
var confirmationMsgTitle,
|
||||
confirmationMsgBody,
|
||||
currentSessionId = this.entitlementModel.get('currentSessionId'),
|
||||
newSessionId = this.$('.session-select').find('option:selected').data('session_id');
|
||||
// Disable the button if the user is already enrolled in that session.
|
||||
if (currentSessionId === newSessionId) {
|
||||
enrollBtnInitial.addClass('disabled');
|
||||
this.removeDialog(enrollBtnInitial);
|
||||
return;
|
||||
}
|
||||
enrollBtnInitial.removeClass('disabled');
|
||||
|
||||
// Update the button popover text to enable two step authentication.
|
||||
if (newSessionId) {
|
||||
confirmationMsgTitle = !currentSessionId ?
|
||||
gettext('Are you sure you want to select this session?') :
|
||||
gettext('Are you sure you want to change to a different session?');
|
||||
confirmationMsgBody = !currentSessionId ? '' :
|
||||
gettext('Any course progress or grades from your current session will be lost.');
|
||||
} else {
|
||||
confirmationMsgTitle = gettext('Are you sure that you want to leave this session?');
|
||||
confirmationMsgBody = gettext('Any course progress or grades from your current session will be lost.'); // eslint-disable-line max-len
|
||||
}
|
||||
// Update button text specifying if the user is initially enrolling,
|
||||
// changing or leaving a session.
|
||||
if (newSessionId) {
|
||||
enrollText = currentSessionId ? gettext('Change Session') : gettext('Select Session');
|
||||
} else {
|
||||
enrollText = gettext('Leave Current Session');
|
||||
}
|
||||
enrollBtnInitial.text(enrollText);
|
||||
this.initializeVerificationDialog(enrollBtnInitial);
|
||||
}
|
||||
|
||||
// Re-initialize the popover
|
||||
invokingElement.popover({
|
||||
placement: 'bottom',
|
||||
container: this.$el,
|
||||
html: true,
|
||||
trigger: 'click',
|
||||
content: this.verificationTpl({
|
||||
confirmationMsgTitle: confirmationMsgTitle,
|
||||
confirmationMsgBody: confirmationMsgBody
|
||||
}).text
|
||||
});
|
||||
},
|
||||
toggleSessionSelectionPanel() {
|
||||
/*
|
||||
Opens and closes the session selection panel.
|
||||
*/
|
||||
this.$el.toggleClass('hidden');
|
||||
if (!this.$el.hasClass('hidden')) {
|
||||
// Set focus to the session selection for a11y purposes
|
||||
this.$('.session-select').focus();
|
||||
this.hideDialog(this.$('.enroll-btn-initial'));
|
||||
}
|
||||
this.updateEnrollBtn();
|
||||
}
|
||||
|
||||
removeDialog: function(el) {
|
||||
/* Removes the Bootstrap v4 dialog modal from the update session enrollment button. */
|
||||
var $el = el instanceof jQuery ? el : this.$('.enroll-btn-initial');
|
||||
if (this.$('popover').length) {
|
||||
$el.popover('dispose');
|
||||
}
|
||||
},
|
||||
initializeVerificationDialog(invokingElement) {
|
||||
/*
|
||||
Instantiates an instance of the Bootstrap v4 dialog modal and attaches it to the
|
||||
passed in element.
|
||||
|
||||
hideDialog: function(el, returnFocus) {
|
||||
/* Hides the modal if it is visible without removing it from the DOM. */
|
||||
var $el = el instanceof jQuery ? el : this.$('.enroll-btn-initial');
|
||||
if (this.$('.popover:visible').length) {
|
||||
$el.popover('hide');
|
||||
if (returnFocus) {
|
||||
$el.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
This dialog acts as the second step in verifying the user's action to select, change
|
||||
or leave an available course session.
|
||||
*/
|
||||
let confirmationMsgTitle;
|
||||
let confirmationMsgBody;
|
||||
const currentSessionId = this.entitlementModel.get('currentSessionId');
|
||||
const newSessionId = this.$('.session-select').find('option:selected').data('session_id');
|
||||
|
||||
handleVerificationPopoverA11y: function(e) {
|
||||
/* Ensure that the second step verification popover is treated as an a11y compliant dialog */
|
||||
var $nextButton,
|
||||
$verificationOption = $(e.target),
|
||||
openButton = $(e.target).closest('.course-entitlement-selection-container')
|
||||
.find('.enroll-btn-initial');
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
$nextButton = $verificationOption.is(':first-child') ?
|
||||
// Update the button popover text to enable two step authentication.
|
||||
if (newSessionId) {
|
||||
confirmationMsgTitle = !currentSessionId ?
|
||||
gettext('Are you sure you want to select this session?') :
|
||||
gettext('Are you sure you want to change to a different session?');
|
||||
confirmationMsgBody = !currentSessionId ? '' :
|
||||
gettext('Any course progress or grades from your current session will be lost.');
|
||||
} else {
|
||||
confirmationMsgTitle = gettext('Are you sure that you want to leave this session?');
|
||||
confirmationMsgBody = gettext('Any course progress or grades from your current session will be lost.'); // eslint-disable-line max-len
|
||||
}
|
||||
|
||||
// Re-initialize the popover
|
||||
invokingElement.popover({
|
||||
placement: 'bottom',
|
||||
container: this.$el,
|
||||
html: true,
|
||||
trigger: 'click',
|
||||
content: this.verificationTpl({
|
||||
confirmationMsgTitle,
|
||||
confirmationMsgBody,
|
||||
}).text,
|
||||
});
|
||||
}
|
||||
|
||||
removeDialog(el) {
|
||||
/* Removes the Bootstrap v4 dialog modal from the update session enrollment button. */
|
||||
const $el = el instanceof jQuery ? el : this.$('.enroll-btn-initial');
|
||||
if (this.$('popover').length) {
|
||||
$el.popover('dispose');
|
||||
}
|
||||
}
|
||||
|
||||
hideDialog(el, returnFocus) {
|
||||
/* Hides the modal if it is visible without removing it from the DOM. */
|
||||
const $el = el instanceof jQuery ? el : this.$('.enroll-btn-initial');
|
||||
if (this.$('.popover:visible').length) {
|
||||
$el.popover('hide');
|
||||
if (returnFocus) {
|
||||
$el.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleVerificationPopoverA11y(e) {
|
||||
/* Ensure that the second step verification popover is treated as an a11y compliant dialog */
|
||||
let $nextButton;
|
||||
const $verificationOption = $(e.target);
|
||||
const openButton = $(e.target).closest('.course-entitlement-selection-container')
|
||||
.find('.enroll-btn-initial');
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
$nextButton = $verificationOption.is(':first-child') ?
|
||||
$verificationOption.next('.final-confirmation-btn') :
|
||||
$verificationOption.prev('.final-confirmation-btn');
|
||||
$nextButton.focus();
|
||||
} else if (e.key === 'Escape') {
|
||||
this.hideDialog(openButton);
|
||||
openButton.focus();
|
||||
}
|
||||
},
|
||||
$nextButton.focus();
|
||||
} else if (e.key === 'Escape') {
|
||||
this.hideDialog(openButton);
|
||||
openButton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
formatCourseHomeUrl: function(sessionKey) {
|
||||
/*
|
||||
Takes the base course home URL and updates it with the new session id, leveraging the
|
||||
the fact that all course keys contain a '+' symbol.
|
||||
*/
|
||||
var oldSessionKey = this.courseHomeUrl.split('/')
|
||||
formatCourseHomeUrl(sessionKey) {
|
||||
/*
|
||||
Takes the base course home URL and updates it with the new session id, leveraging the
|
||||
the fact that all course keys contain a '+' symbol.
|
||||
*/
|
||||
const oldSessionKey = this.courseHomeUrl.split('/')
|
||||
.filter(
|
||||
function(urlParam) {
|
||||
return urlParam.indexOf('+') > 0;
|
||||
}
|
||||
urlParam => urlParam.indexOf('+') > 0,
|
||||
)[0];
|
||||
return this.courseHomeUrl.replace(oldSessionKey, sessionKey);
|
||||
},
|
||||
return this.courseHomeUrl.replace(oldSessionKey, sessionKey);
|
||||
}
|
||||
|
||||
formatDates: function(sessionData) {
|
||||
/*
|
||||
Takes a data object containing the upcoming available sessions for an entitlement and returns
|
||||
the object with a session_dates attribute representing a formatted date string that highlights
|
||||
the start and end dates of the particular session.
|
||||
*/
|
||||
var formattedSessionData = sessionData,
|
||||
startDate,
|
||||
endDate,
|
||||
dateFormat;
|
||||
// Set the date format string to the user's selected language
|
||||
moment.locale(document.documentElement.lang);
|
||||
dateFormat = moment.localeData().longDateFormat('L').indexOf('DD') >
|
||||
moment.localeData().longDateFormat('L').indexOf('MM') ? 'MMMM D, YYYY' : 'D MMMM, YYYY';
|
||||
formatDates(sessionData) {
|
||||
/*
|
||||
Takes a data object containing the upcoming available sessions for an entitlement and returns
|
||||
the object with a session_dates attribute representing a formatted date string that highlights
|
||||
the start and end dates of the particular session.
|
||||
*/
|
||||
const formattedSessionData = sessionData;
|
||||
let startDate;
|
||||
let endDate;
|
||||
// Set the date format string to the user's selected language
|
||||
moment.locale(document.documentElement.lang);
|
||||
const dateFormat = moment.localeData().longDateFormat('L').indexOf('DD') >
|
||||
moment.localeData().longDateFormat('L').indexOf('MM') ? 'MMMM D, YYYY' : 'D MMMM, YYYY';
|
||||
|
||||
return _.map(formattedSessionData, function(session) {
|
||||
var formattedSession = session;
|
||||
startDate = this.formatDate(formattedSession.start, dateFormat);
|
||||
endDate = this.formatDate(formattedSession.end, dateFormat);
|
||||
formattedSession.enrollment_end = this.formatDate(formattedSession.enrollment_end, dateFormat);
|
||||
formattedSession.session_dates = this.courseCardModel.formatDateString({
|
||||
start_date: startDate,
|
||||
advertised_start: session.advertised_start,
|
||||
end_date: endDate,
|
||||
pacing_type: formattedSession.pacing_type
|
||||
});
|
||||
return formattedSession;
|
||||
}, this);
|
||||
},
|
||||
return _.map(formattedSessionData, (session) => {
|
||||
const formattedSession = session;
|
||||
startDate = CourseEntitlementView.formatDate(formattedSession.start, dateFormat);
|
||||
endDate = CourseEntitlementView.formatDate(formattedSession.end, dateFormat);
|
||||
formattedSession.enrollment_end = CourseEntitlementView.formatDate(
|
||||
formattedSession.enrollment_end,
|
||||
dateFormat);
|
||||
formattedSession.session_dates = this.courseCardModel.formatDateString({
|
||||
start_date: startDate,
|
||||
advertised_start: session.advertised_start,
|
||||
end_date: endDate,
|
||||
pacing_type: formattedSession.pacing_type,
|
||||
});
|
||||
return formattedSession;
|
||||
}, this);
|
||||
}
|
||||
|
||||
formatDate: function(date, dateFormat) {
|
||||
return date ? moment((new Date(date))).format(dateFormat) : '';
|
||||
},
|
||||
static formatDate(date, dateFormat) {
|
||||
return date ? moment((new Date(date))).format(dateFormat) : '';
|
||||
}
|
||||
|
||||
getAvailableSessionWithId: function(sessionId) {
|
||||
/* Returns an available session given a sessionId */
|
||||
return this.entitlementModel.get('availableSessions').find(function(session) {
|
||||
return session.session_id === sessionId;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
getAvailableSessionWithId(sessionId) {
|
||||
/* Returns an available session given a sessionId */
|
||||
return this.entitlementModel.get('availableSessions').find(session => session.session_id === sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
export default CourseEntitlementView;
|
||||
|
||||
@@ -1,130 +1,134 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils'
|
||||
],
|
||||
function(Backbone, $, gettext, HtmlUtils) {
|
||||
return Backbone.View.extend({
|
||||
el: '.js-entitlement-unenrollment-modal',
|
||||
closeButtonSelector: '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-close-btn',
|
||||
headerTextSelector: '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-header-text',
|
||||
errorTextSelector: '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-error-text',
|
||||
submitButtonSelector: '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-submit',
|
||||
triggerSelector: '.js-entitlement-action-unenroll',
|
||||
mainPageSelector: '#dashboard-main',
|
||||
genericErrorMsg: gettext('Your unenrollment request could not be processed. Please try again later.'),
|
||||
/* globals gettext */
|
||||
|
||||
initialize: function(options) {
|
||||
var view = this;
|
||||
this.dashboardPath = options.dashboardPath;
|
||||
this.signInPath = options.signInPath;
|
||||
import Backbone from 'backbone';
|
||||
|
||||
this.$submitButton = $(this.submitButtonSelector);
|
||||
this.$headerText = $(this.headerTextSelector);
|
||||
this.$errorText = $(this.errorTextSelector);
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
this.$submitButton.on('click', this.handleSubmit.bind(this));
|
||||
class EntitlementUnenrollmentView extends Backbone.View {
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
el: '.js-entitlement-unenrollment-modal',
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
$(this.triggerSelector).each(function() {
|
||||
var $trigger = $(this);
|
||||
initialize(options) {
|
||||
const view = this;
|
||||
|
||||
$trigger.on('click', view.handleTrigger.bind(view));
|
||||
this.closeButtonSelector = '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-close-btn';
|
||||
this.headerTextSelector = '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-header-text';
|
||||
this.errorTextSelector = '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-error-text';
|
||||
this.submitButtonSelector = '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-submit';
|
||||
this.triggerSelector = '.js-entitlement-action-unenroll';
|
||||
this.mainPageSelector = '#dashboard-main';
|
||||
this.genericErrorMsg = gettext('Your unenrollment request could not be processed. Please try again later.');
|
||||
|
||||
if (window.accessible_modal) {
|
||||
window.accessible_modal(
|
||||
'#' + $trigger.attr('id'),
|
||||
view.closeButtonSelector,
|
||||
'#' + view.$el.attr('id'),
|
||||
view.mainPageSelector
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
this.dashboardPath = options.dashboardPath;
|
||||
this.signInPath = options.signInPath;
|
||||
|
||||
handleTrigger: function(event) {
|
||||
var $trigger = $(event.target),
|
||||
courseName = $trigger.data('courseName'),
|
||||
courseNumber = $trigger.data('courseNumber'),
|
||||
apiEndpoint = $trigger.data('entitlementApiEndpoint');
|
||||
this.$submitButton = $(this.submitButtonSelector);
|
||||
this.$headerText = $(this.headerTextSelector);
|
||||
this.$errorText = $(this.errorTextSelector);
|
||||
|
||||
this.resetModal();
|
||||
this.setHeaderText(courseName, courseNumber);
|
||||
this.setSubmitData(apiEndpoint);
|
||||
this.$el.css('position', 'fixed');
|
||||
},
|
||||
this.$submitButton.on('click', this.handleSubmit.bind(this));
|
||||
|
||||
handleSubmit: function() {
|
||||
var apiEndpoint = this.$submitButton.data('entitlementApiEndpoint');
|
||||
$(this.triggerSelector).each(function setUpTrigger() {
|
||||
const $trigger = $(this);
|
||||
|
||||
if (apiEndpoint === undefined) {
|
||||
this.setError(this.genericErrorMsg);
|
||||
return;
|
||||
}
|
||||
$trigger.on('click', view.handleTrigger.bind(view));
|
||||
|
||||
this.$submitButton.prop('disabled', true);
|
||||
$.ajax({
|
||||
url: apiEndpoint,
|
||||
method: 'DELETE',
|
||||
complete: this.onComplete.bind(this)
|
||||
});
|
||||
},
|
||||
if (window.accessible_modal) {
|
||||
window.accessible_modal(
|
||||
`#${$trigger.attr('id')}`,
|
||||
view.closeButtonSelector,
|
||||
`#${view.$el.attr('id')}`,
|
||||
view.mainPageSelector,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resetModal: function() {
|
||||
this.$submitButton.removeData();
|
||||
this.$submitButton.prop('disabled', false);
|
||||
this.$headerText.empty();
|
||||
this.$errorText.removeClass('entitlement-unenrollment-modal-error-text-visible');
|
||||
this.$errorText.empty();
|
||||
},
|
||||
handleTrigger(event) {
|
||||
const $trigger = $(event.target);
|
||||
const courseName = $trigger.data('courseName');
|
||||
const courseNumber = $trigger.data('courseNumber');
|
||||
const apiEndpoint = $trigger.data('entitlementApiEndpoint');
|
||||
|
||||
setError: function(message) {
|
||||
this.$submitButton.prop('disabled', true);
|
||||
this.$errorText.empty();
|
||||
HtmlUtils.setHtml(
|
||||
this.resetModal();
|
||||
this.setHeaderText(courseName, courseNumber);
|
||||
this.setSubmitData(apiEndpoint);
|
||||
this.$el.css('position', 'fixed');
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
const apiEndpoint = this.$submitButton.data('entitlementApiEndpoint');
|
||||
|
||||
if (apiEndpoint === undefined) {
|
||||
this.setError(this.genericErrorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$submitButton.prop('disabled', true);
|
||||
$.ajax({
|
||||
url: apiEndpoint,
|
||||
method: 'DELETE',
|
||||
complete: this.onComplete.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
resetModal() {
|
||||
this.$submitButton.removeData();
|
||||
this.$submitButton.prop('disabled', false);
|
||||
this.$headerText.empty();
|
||||
this.$errorText.removeClass('entitlement-unenrollment-modal-error-text-visible');
|
||||
this.$errorText.empty();
|
||||
}
|
||||
|
||||
setError(message) {
|
||||
this.$submitButton.prop('disabled', true);
|
||||
this.$errorText.empty();
|
||||
HtmlUtils.setHtml(
|
||||
this.$errorText,
|
||||
message
|
||||
message,
|
||||
);
|
||||
this.$errorText.addClass('entitlement-unenrollment-modal-error-text-visible');
|
||||
},
|
||||
this.$errorText.addClass('entitlement-unenrollment-modal-error-text-visible');
|
||||
}
|
||||
|
||||
setHeaderText: function(courseName, courseNumber) {
|
||||
this.$headerText.empty();
|
||||
HtmlUtils.setHtml(
|
||||
this.$headerText,
|
||||
HtmlUtils.interpolateHtml(
|
||||
gettext('Are you sure you want to unenroll from {courseName} ({courseNumber})? You will be refunded the amount you paid.'), // eslint-disable-line max-len
|
||||
{
|
||||
courseName: courseName,
|
||||
courseNumber: courseNumber
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
setSubmitData: function(apiEndpoint) {
|
||||
this.$submitButton.removeData();
|
||||
this.$submitButton.data('entitlementApiEndpoint', apiEndpoint);
|
||||
},
|
||||
|
||||
onComplete: function(xhr) {
|
||||
var status = xhr.status,
|
||||
message = xhr.responseJSON && xhr.responseJSON.detail;
|
||||
|
||||
if (status === 204) {
|
||||
this.redirectTo(this.dashboardPath);
|
||||
} else if (status === 401 && message === 'Authentication credentials were not provided.') {
|
||||
this.redirectTo(this.signInPath + '?next=' + encodeURIComponent(this.dashboardPath));
|
||||
} else {
|
||||
this.setError(this.genericErrorMsg);
|
||||
}
|
||||
},
|
||||
|
||||
redirectTo: function(path) {
|
||||
window.location.href = path;
|
||||
}
|
||||
});
|
||||
}
|
||||
setHeaderText(courseName, courseNumber) {
|
||||
this.$headerText.empty();
|
||||
HtmlUtils.setHtml(
|
||||
this.$headerText,
|
||||
HtmlUtils.interpolateHtml(
|
||||
gettext('Are you sure you want to unenroll from {courseName} ({courseNumber})? You will be refunded the amount you paid.'), // eslint-disable-line max-len
|
||||
{
|
||||
courseName,
|
||||
courseNumber,
|
||||
},
|
||||
),
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
}
|
||||
|
||||
setSubmitData(apiEndpoint) {
|
||||
this.$submitButton.removeData();
|
||||
this.$submitButton.data('entitlementApiEndpoint', apiEndpoint);
|
||||
}
|
||||
|
||||
onComplete(xhr) {
|
||||
const status = xhr.status;
|
||||
const message = xhr.responseJSON && xhr.responseJSON.detail;
|
||||
|
||||
if (status === 204) {
|
||||
EntitlementUnenrollmentView.redirectTo(this.dashboardPath);
|
||||
} else if (status === 401 && message === 'Authentication credentials were not provided.') {
|
||||
EntitlementUnenrollmentView.redirectTo(`${this.signInPath}?next=${encodeURIComponent(this.dashboardPath)}`);
|
||||
} else {
|
||||
this.setError(this.genericErrorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
static redirectTo(path) {
|
||||
window.location.href = path;
|
||||
}
|
||||
}
|
||||
|
||||
export default EntitlementUnenrollmentView;
|
||||
|
||||
@@ -1,33 +1,20 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/expired_notification.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
expiredNotificationTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
expiredNotificationTpl: HtmlUtils.template(expiredNotificationTpl),
|
||||
import Backbone from 'backbone';
|
||||
|
||||
initialize: function(options) {
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
},
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
render: function() {
|
||||
var data = this.model.toJSON();
|
||||
HtmlUtils.setHtml(this.$el, this.expiredNotificationTpl(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
import expiredNotificationTpl from '../../../templates/learner_dashboard/expired_notification.underscore';
|
||||
|
||||
class ExpiredNotificationView extends Backbone.View {
|
||||
initialize(options) {
|
||||
this.expiredNotificationTpl = HtmlUtils.template(expiredNotificationTpl);
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = this.model.toJSON();
|
||||
HtmlUtils.setHtml(this.$el, this.expiredNotificationTpl(data));
|
||||
}
|
||||
}
|
||||
|
||||
export default ExpiredNotificationView;
|
||||
|
||||
@@ -1,44 +1,33 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
import _ from 'underscore';
|
||||
import Backbone from 'backbone';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'text!../../../templates/learner_dashboard/explore_new_programs.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
exploreTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
el: '.program-advertise',
|
||||
import exploreTpl from '../../../templates/learner_dashboard/explore_new_programs.underscore';
|
||||
|
||||
tpl: _.template(exploreTpl),
|
||||
class ExploreNewProgramsView extends Backbone.View {
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
el: '.program-advertise',
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
initialize: function(data) {
|
||||
this.context = data.context;
|
||||
this.$parentEl = $(this.parentEl);
|
||||
initialize(data) {
|
||||
this.tpl = _.template(exploreTpl);
|
||||
this.context = data.context;
|
||||
this.$parentEl = $(this.parentEl);
|
||||
|
||||
if (this.context.marketingUrl) {
|
||||
// Only render if there is a link
|
||||
this.render();
|
||||
} else {
|
||||
/**
|
||||
* If not rendering remove el because
|
||||
* styles are applied to it
|
||||
*/
|
||||
this.remove();
|
||||
}
|
||||
},
|
||||
if (this.context.marketingUrl) {
|
||||
// Only render if there is a link
|
||||
this.render();
|
||||
} else {
|
||||
// If not rendering, remove el because styles are applied to it
|
||||
this.remove();
|
||||
}
|
||||
}
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.tpl(this.context));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
render() {
|
||||
this.$el.html(this.tpl(this.context));
|
||||
}
|
||||
}
|
||||
|
||||
export default ExploreNewProgramsView;
|
||||
|
||||
@@ -1,114 +1,102 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
/* globals gettext */
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'text!../../../templates/learner_dashboard/program_card.underscore',
|
||||
'picturefill'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
programCardTpl,
|
||||
picturefill
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
import _ from 'underscore';
|
||||
import Backbone from 'backbone';
|
||||
import picturefill from 'picturefill';
|
||||
|
||||
className: 'program-card',
|
||||
import programCardTpl from '../../../templates/learner_dashboard/program_card.underscore';
|
||||
|
||||
attributes: function() {
|
||||
return {
|
||||
'aria-labelledby': 'program-' + this.model.get('uuid'),
|
||||
role: 'group'
|
||||
};
|
||||
},
|
||||
class ProgramCardView extends Backbone.View {
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
className: 'program-card',
|
||||
attributes: function attr() {
|
||||
return {
|
||||
'aria-labelledby': `program-${this.model.get('uuid')}`,
|
||||
role: 'group',
|
||||
};
|
||||
},
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
tpl: _.template(programCardTpl),
|
||||
initialize(data) {
|
||||
this.tpl = _.template(programCardTpl);
|
||||
this.progressCollection = data.context.progressCollection;
|
||||
if (this.progressCollection) {
|
||||
this.progressModel = this.progressCollection.findWhere({
|
||||
uuid: this.model.get('uuid'),
|
||||
});
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
initialize: function(data) {
|
||||
this.progressCollection = data.context.progressCollection;
|
||||
if (this.progressCollection) {
|
||||
this.progressModel = this.progressCollection.findWhere({
|
||||
uuid: this.model.get('uuid')
|
||||
});
|
||||
}
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var orgList = _.map(this.model.get('authoring_organizations'), function(org) {
|
||||
return gettext(org.key);
|
||||
}),
|
||||
data = $.extend(
|
||||
this.model.toJSON(),
|
||||
this.getProgramProgress(),
|
||||
{orgList: orgList.join(' ')}
|
||||
);
|
||||
|
||||
this.$el.html(this.tpl(data));
|
||||
this.postRender();
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
if (navigator.userAgent.indexOf('MSIE') !== -1 ||
|
||||
navigator.appVersion.indexOf('Trident/') > 0) {
|
||||
/* Microsoft Internet Explorer detected in. */
|
||||
window.setTimeout(function() {
|
||||
this.reLoadBannerImage();
|
||||
}.bind(this), 100);
|
||||
}
|
||||
},
|
||||
|
||||
// Calculate counts for progress and percentages for styling
|
||||
getProgramProgress: function() {
|
||||
var progress = this.progressModel ? this.progressModel.toJSON() : false;
|
||||
|
||||
if (progress) {
|
||||
progress.total = progress.completed +
|
||||
progress.in_progress +
|
||||
progress.not_started;
|
||||
|
||||
progress.percentage = {
|
||||
completed: this.getWidth(progress.completed, progress.total),
|
||||
in_progress: this.getWidth(progress.in_progress, progress.total)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
progress: progress
|
||||
};
|
||||
},
|
||||
|
||||
getWidth: function(val, total) {
|
||||
var int = (val / total) * 100;
|
||||
|
||||
return int + '%';
|
||||
},
|
||||
|
||||
// Defer loading the rest of the page to limit FOUC
|
||||
reLoadBannerImage: function() {
|
||||
var $img = this.$('.program_card .banner-image'),
|
||||
imgSrcAttr = $img ? $img.attr('src') : {};
|
||||
|
||||
if (!imgSrcAttr || imgSrcAttr.length < 0) {
|
||||
try {
|
||||
this.reEvaluatePicture();
|
||||
} catch (err) {
|
||||
// Swallow the error here
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
reEvaluatePicture: function() {
|
||||
picturefill({
|
||||
reevaluate: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const orgList = _.map(this.model.get('authoring_organizations'), org => gettext(org.key));
|
||||
const data = $.extend(
|
||||
this.model.toJSON(),
|
||||
this.getProgramProgress(),
|
||||
{ orgList: orgList.join(' ') },
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
this.$el.html(this.tpl(data));
|
||||
this.postRender();
|
||||
}
|
||||
|
||||
postRender() {
|
||||
if (navigator.userAgent.indexOf('MSIE') !== -1 ||
|
||||
navigator.appVersion.indexOf('Trident/') > 0) {
|
||||
/* Microsoft Internet Explorer detected in. */
|
||||
window.setTimeout(() => {
|
||||
this.reLoadBannerImage();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate counts for progress and percentages for styling
|
||||
getProgramProgress() {
|
||||
const progress = this.progressModel ? this.progressModel.toJSON() : false;
|
||||
|
||||
if (progress) {
|
||||
progress.total = progress.completed +
|
||||
progress.in_progress +
|
||||
progress.not_started;
|
||||
|
||||
progress.percentage = {
|
||||
completed: ProgramCardView.getWidth(progress.completed, progress.total),
|
||||
in_progress: ProgramCardView.getWidth(progress.in_progress, progress.total),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
progress,
|
||||
};
|
||||
}
|
||||
|
||||
static getWidth(val, total) {
|
||||
const int = (val / total) * 100;
|
||||
return `${int}%`;
|
||||
}
|
||||
|
||||
// Defer loading the rest of the page to limit FOUC
|
||||
reLoadBannerImage() {
|
||||
const $img = this.$('.program_card .banner-image');
|
||||
const imgSrcAttr = $img ? $img.attr('src') : {};
|
||||
|
||||
if (!imgSrcAttr || imgSrcAttr.length < 0) {
|
||||
try {
|
||||
ProgramCardView.reEvaluatePicture();
|
||||
} catch (err) {
|
||||
// Swallow the error here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static reEvaluatePicture() {
|
||||
picturefill({
|
||||
reevaluate: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ProgramCardView;
|
||||
|
||||
@@ -1,97 +1,82 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
/* globals gettext */
|
||||
|
||||
define([
|
||||
'backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'edx-ui-toolkit/js/utils/string-utils',
|
||||
'common/js/components/views/progress_circle_view',
|
||||
'js/learner_dashboard/views/certificate_list_view',
|
||||
'text!../../../templates/learner_dashboard/program_details_sidebar.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
StringUtils,
|
||||
ProgramProgressView,
|
||||
CertificateView,
|
||||
sidebarTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
tpl: HtmlUtils.template(sidebarTpl),
|
||||
import Backbone from 'backbone';
|
||||
|
||||
initialize: function(options) {
|
||||
this.courseModel = options.courseModel || {};
|
||||
this.certificateCollection = options.certificateCollection || [];
|
||||
this.programCertificate = this.getProgramCertificate();
|
||||
this.render();
|
||||
},
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
import StringUtils from 'edx-ui-toolkit/js/utils/string-utils';
|
||||
|
||||
render: function() {
|
||||
var data = $.extend({}, this.model.toJSON(), {
|
||||
programCertificate: this.programCertificate ?
|
||||
this.programCertificate.toJSON() : {}
|
||||
});
|
||||
import CertificateView from './certificate_list_view';
|
||||
import ProgramProgressView from './progress_circle_view';
|
||||
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
},
|
||||
import sidebarTpl from '../../../templates/learner_dashboard/program_details_sidebar.underscore';
|
||||
|
||||
postRender: function() {
|
||||
if (!this.programCertificate) {
|
||||
this.progressModel = new Backbone.Model({
|
||||
title: StringUtils.interpolate(
|
||||
gettext('{type} Progress'),
|
||||
{type: this.model.get('type')}
|
||||
),
|
||||
label: gettext('Earned Certificates'),
|
||||
progress: {
|
||||
completed: this.courseModel.get('completed').length,
|
||||
in_progress: this.courseModel.get('in_progress').length,
|
||||
not_started: this.courseModel.get('not_started').length
|
||||
}
|
||||
});
|
||||
class ProgramDetailsSidebarView extends Backbone.View {
|
||||
initialize(options) {
|
||||
this.tpl = HtmlUtils.template(sidebarTpl);
|
||||
this.courseModel = options.courseModel || {};
|
||||
this.certificateCollection = options.certificateCollection || [];
|
||||
this.programCertificate = this.getProgramCertificate();
|
||||
this.render();
|
||||
}
|
||||
|
||||
this.programProgressView = new ProgramProgressView({
|
||||
el: '.js-program-progress',
|
||||
model: this.progressModel
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const data = $.extend({}, this.model.toJSON(), {
|
||||
programCertificate: this.programCertificate ?
|
||||
this.programCertificate.toJSON() : {},
|
||||
});
|
||||
|
||||
if (this.certificateCollection.length) {
|
||||
this.certificateView = new CertificateView({
|
||||
el: '.js-course-certificates',
|
||||
collection: this.certificateCollection,
|
||||
title: gettext('Earned Certificates')
|
||||
});
|
||||
}
|
||||
},
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
}
|
||||
|
||||
getProgramCertificate: function() {
|
||||
var certificate = this.certificateCollection.findWhere({type: 'program'}),
|
||||
base = '/static/images/programs/program-certificate-';
|
||||
postRender() {
|
||||
if (!this.programCertificate) {
|
||||
this.progressModel = new Backbone.Model({
|
||||
title: StringUtils.interpolate(
|
||||
gettext('{type} Progress'),
|
||||
{ type: this.model.get('type') },
|
||||
),
|
||||
label: gettext('Earned Certificates'),
|
||||
progress: {
|
||||
completed: this.courseModel.get('completed').length,
|
||||
in_progress: this.courseModel.get('in_progress').length,
|
||||
not_started: this.courseModel.get('not_started').length,
|
||||
},
|
||||
});
|
||||
|
||||
if (certificate) {
|
||||
certificate.set({
|
||||
img: base + this.getType() + '.gif'
|
||||
});
|
||||
}
|
||||
this.programProgressView = new ProgramProgressView({
|
||||
el: '.js-program-progress',
|
||||
model: this.progressModel,
|
||||
});
|
||||
}
|
||||
|
||||
return certificate;
|
||||
},
|
||||
if (this.certificateCollection.length) {
|
||||
this.certificateView = new CertificateView({
|
||||
el: '.js-course-certificates',
|
||||
collection: this.certificateCollection,
|
||||
title: gettext('Earned Certificates'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getType: function() {
|
||||
var type = this.model.get('type').toLowerCase();
|
||||
getProgramCertificate() {
|
||||
const certificate = this.certificateCollection.findWhere({ type: 'program' });
|
||||
const base = '/static/images/programs/program-certificate-';
|
||||
|
||||
return type.replace(/\s+/g, '-');
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
if (certificate) {
|
||||
certificate.set({
|
||||
img: `${base + this.getType()}.gif`,
|
||||
});
|
||||
}
|
||||
|
||||
return certificate;
|
||||
}
|
||||
|
||||
getType() {
|
||||
const type = this.model.get('type').toLowerCase();
|
||||
|
||||
return type.replace(/\s+/g, '-');
|
||||
}
|
||||
}
|
||||
|
||||
export default ProgramDetailsSidebarView;
|
||||
|
||||
@@ -1,137 +1,128 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'js/learner_dashboard/collections/course_card_collection',
|
||||
'js/learner_dashboard/views/program_header_view',
|
||||
'js/learner_dashboard/views/collection_list_view',
|
||||
'js/learner_dashboard/views/course_card_view',
|
||||
'js/learner_dashboard/views/program_details_sidebar_view',
|
||||
'text!../../../templates/learner_dashboard/program_details_view.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
CourseCardCollection,
|
||||
HeaderView,
|
||||
CollectionListView,
|
||||
CourseCardView,
|
||||
SidebarView,
|
||||
pageTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
el: '.js-program-details-wrapper',
|
||||
/* globals gettext */
|
||||
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
import Backbone from 'backbone';
|
||||
|
||||
events: {
|
||||
'click .complete-program': 'trackPurchase'
|
||||
},
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
initialize: function(options) {
|
||||
this.options = options;
|
||||
this.programModel = new Backbone.Model(this.options.programData);
|
||||
this.courseData = new Backbone.Model(this.options.courseData);
|
||||
this.certificateCollection = new Backbone.Collection(this.options.certificateData);
|
||||
this.completedCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('completed') || [],
|
||||
this.options.userPreferences
|
||||
);
|
||||
this.inProgressCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('in_progress') || [],
|
||||
this.options.userPreferences
|
||||
);
|
||||
this.remainingCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('not_started') || [],
|
||||
this.options.userPreferences
|
||||
);
|
||||
import CollectionListView from './collection_list_view';
|
||||
import CourseCardCollection from '../collections/course_card_collection';
|
||||
import CourseCardView from './course_card_view';
|
||||
import HeaderView from './program_header_view';
|
||||
import SidebarView from './program_details_sidebar_view';
|
||||
|
||||
this.render();
|
||||
},
|
||||
import pageTpl from '../../../templates/learner_dashboard/program_details_view.underscore';
|
||||
|
||||
getUrl: function(base, programData) {
|
||||
if (programData.uuid) {
|
||||
return base + '&bundle=' + encodeURIComponent(programData.uuid);
|
||||
}
|
||||
return base;
|
||||
},
|
||||
class ProgramDetailsView extends Backbone.View {
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
el: '.js-program-details-wrapper',
|
||||
events: {
|
||||
'click .complete-program': 'trackPurchase',
|
||||
},
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
render: function() {
|
||||
var completedCount = this.completedCourseCollection.length,
|
||||
inProgressCount = this.inProgressCourseCollection.length,
|
||||
remainingCount = this.remainingCourseCollection.length,
|
||||
totalCount = completedCount + inProgressCount + remainingCount,
|
||||
buyButtonUrl = this.getUrl(this.options.urls.buy_button_url, this.options.programData),
|
||||
data = {
|
||||
totalCount: totalCount,
|
||||
inProgressCount: inProgressCount,
|
||||
remainingCount: remainingCount,
|
||||
completedCount: completedCount,
|
||||
completeProgramURL: buyButtonUrl
|
||||
};
|
||||
data = $.extend(data, this.programModel.toJSON());
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
this.headerView = new HeaderView({
|
||||
model: new Backbone.Model(this.options)
|
||||
});
|
||||
|
||||
if (this.remainingCourseCollection.length > 0) {
|
||||
new CollectionListView({
|
||||
el: '.js-course-list-remaining',
|
||||
childView: CourseCardView,
|
||||
collection: this.remainingCourseCollection,
|
||||
context: $.extend(this.options, {collectionCourseStatus: 'remaining'})
|
||||
}).render();
|
||||
}
|
||||
|
||||
if (this.completedCourseCollection.length > 0) {
|
||||
new CollectionListView({
|
||||
el: '.js-course-list-completed',
|
||||
childView: CourseCardView,
|
||||
collection: this.completedCourseCollection,
|
||||
context: $.extend(this.options, {collectionCourseStatus: 'completed'})
|
||||
}).render();
|
||||
}
|
||||
|
||||
if (this.inProgressCourseCollection.length > 0) {
|
||||
// This is last because the context is modified below
|
||||
new CollectionListView({
|
||||
el: '.js-course-list-in-progress',
|
||||
childView: CourseCardView,
|
||||
collection: this.inProgressCourseCollection,
|
||||
context: $.extend(this.options,
|
||||
{enrolled: gettext('Enrolled'), collectionCourseStatus: 'in_progress'}
|
||||
)
|
||||
}).render();
|
||||
}
|
||||
|
||||
this.sidebarView = new SidebarView({
|
||||
el: '.js-program-sidebar',
|
||||
model: this.programModel,
|
||||
courseModel: this.courseData,
|
||||
certificateCollection: this.certificateCollection
|
||||
});
|
||||
},
|
||||
|
||||
trackPurchase: function() {
|
||||
var data = this.options.programData;
|
||||
window.analytics.track('edx.bi.user.dashboard.program.purchase', {
|
||||
category: data.variant + ' bundle',
|
||||
label: data.title,
|
||||
uuid: data.uuid
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
initialize(options) {
|
||||
this.options = options;
|
||||
this.tpl = HtmlUtils.template(pageTpl);
|
||||
this.programModel = new Backbone.Model(this.options.programData);
|
||||
this.courseData = new Backbone.Model(this.options.courseData);
|
||||
this.certificateCollection = new Backbone.Collection(this.options.certificateData);
|
||||
this.completedCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('completed') || [],
|
||||
this.options.userPreferences,
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
this.inProgressCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('in_progress') || [],
|
||||
this.options.userPreferences,
|
||||
);
|
||||
this.remainingCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('not_started') || [],
|
||||
this.options.userPreferences,
|
||||
);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static getUrl(base, programData) {
|
||||
if (programData.uuid) {
|
||||
return `${base}&bundle=${encodeURIComponent(programData.uuid)}`;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
render() {
|
||||
const completedCount = this.completedCourseCollection.length;
|
||||
const inProgressCount = this.inProgressCourseCollection.length;
|
||||
const remainingCount = this.remainingCourseCollection.length;
|
||||
const totalCount = completedCount + inProgressCount + remainingCount;
|
||||
const buyButtonUrl = ProgramDetailsView.getUrl(
|
||||
this.options.urls.buy_button_url,
|
||||
this.options.programData);
|
||||
let data = {
|
||||
totalCount,
|
||||
inProgressCount,
|
||||
remainingCount,
|
||||
completedCount,
|
||||
completeProgramURL: buyButtonUrl,
|
||||
};
|
||||
data = $.extend(data, this.programModel.toJSON());
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
}
|
||||
|
||||
postRender() {
|
||||
this.headerView = new HeaderView({
|
||||
model: new Backbone.Model(this.options),
|
||||
});
|
||||
|
||||
if (this.remainingCourseCollection.length > 0) {
|
||||
new CollectionListView({
|
||||
el: '.js-course-list-remaining',
|
||||
childView: CourseCardView,
|
||||
collection: this.remainingCourseCollection,
|
||||
context: $.extend(this.options, { collectionCourseStatus: 'remaining' }),
|
||||
}).render();
|
||||
}
|
||||
|
||||
if (this.completedCourseCollection.length > 0) {
|
||||
new CollectionListView({
|
||||
el: '.js-course-list-completed',
|
||||
childView: CourseCardView,
|
||||
collection: this.completedCourseCollection,
|
||||
context: $.extend(this.options, { collectionCourseStatus: 'completed' }),
|
||||
}).render();
|
||||
}
|
||||
|
||||
if (this.inProgressCourseCollection.length > 0) {
|
||||
// This is last because the context is modified below
|
||||
new CollectionListView({
|
||||
el: '.js-course-list-in-progress',
|
||||
childView: CourseCardView,
|
||||
collection: this.inProgressCourseCollection,
|
||||
context: $.extend(this.options,
|
||||
{ enrolled: gettext('Enrolled'), collectionCourseStatus: 'in_progress' },
|
||||
),
|
||||
}).render();
|
||||
}
|
||||
|
||||
this.sidebarView = new SidebarView({
|
||||
el: '.js-program-sidebar',
|
||||
model: this.programModel,
|
||||
courseModel: this.courseData,
|
||||
certificateCollection: this.certificateCollection,
|
||||
});
|
||||
}
|
||||
|
||||
trackPurchase() {
|
||||
const data = this.options.programData;
|
||||
window.analytics.track('edx.bi.user.dashboard.program.purchase', {
|
||||
category: `${data.variant} bundle`,
|
||||
label: data.title,
|
||||
uuid: data.uuid,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ProgramDetailsView;
|
||||
|
||||
@@ -1,57 +1,55 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
import Backbone from 'backbone';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/program_header_view.underscore',
|
||||
'text!../../../images/programs/micromasters-program-details.svg',
|
||||
'text!../../../images/programs/xseries-program-details.svg',
|
||||
'text!../../../images/programs/professional-certificate-program-details.svg'
|
||||
],
|
||||
function(Backbone, $, HtmlUtils, pageTpl, MicroMastersLogo,
|
||||
XSeriesLogo, ProfessionalCertificateLogo) {
|
||||
return Backbone.View.extend({
|
||||
breakpoints: {
|
||||
min: {
|
||||
medium: '768px',
|
||||
large: '1180px'
|
||||
}
|
||||
},
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
el: '.js-program-header',
|
||||
import pageTpl from '../../../templates/learner_dashboard/program_header_view.underscore';
|
||||
import MicroMastersLogo from '../../../images/programs/micromasters-program-details.svg';
|
||||
import XSeriesLogo from '../../../images/programs/xseries-program-details.svg';
|
||||
import ProfessionalCertificateLogo from '../../../images/programs/professional-certificate-program-details.svg';
|
||||
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
class ProgramHeaderView extends Backbone.View {
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
el: '.js-program-header',
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
initialize: function() {
|
||||
this.render();
|
||||
},
|
||||
initialize() {
|
||||
this.breakpoints = {
|
||||
min: {
|
||||
medium: '768px',
|
||||
large: '1180px',
|
||||
},
|
||||
};
|
||||
this.tpl = HtmlUtils.template(pageTpl);
|
||||
this.render();
|
||||
}
|
||||
|
||||
getLogo: function() {
|
||||
var logo = false,
|
||||
type = this.model.get('programData').type;
|
||||
getLogo() {
|
||||
const type = this.model.get('programData').type;
|
||||
let logo = false;
|
||||
|
||||
if (type === 'MicroMasters') {
|
||||
logo = MicroMastersLogo;
|
||||
} else if (type === 'XSeries') {
|
||||
logo = XSeriesLogo;
|
||||
} else if (type === 'Professional Certificate') {
|
||||
logo = ProfessionalCertificateLogo;
|
||||
}
|
||||
return logo;
|
||||
},
|
||||
if (type === 'MicroMasters') {
|
||||
logo = MicroMastersLogo;
|
||||
} else if (type === 'XSeries') {
|
||||
logo = XSeriesLogo;
|
||||
} else if (type === 'Professional Certificate') {
|
||||
logo = ProfessionalCertificateLogo;
|
||||
}
|
||||
return logo;
|
||||
}
|
||||
|
||||
render: function() {
|
||||
var data = $.extend(this.model.toJSON(), {
|
||||
breakpoints: this.breakpoints,
|
||||
logo: this.getLogo()
|
||||
});
|
||||
render() {
|
||||
const data = $.extend(this.model.toJSON(), {
|
||||
breakpoints: this.breakpoints,
|
||||
logo: this.getLogo(),
|
||||
});
|
||||
|
||||
if (this.model.get('programData')) {
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
if (this.model.get('programData')) {
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ProgramHeaderView;
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import _ from 'underscore';
|
||||
import Backbone from 'backbone';
|
||||
|
||||
import progressViewTpl from '../../../templates/learner_dashboard//progress_circle_view.underscore';
|
||||
import progressSegmentTpl from '../../../templates/learner_dashboard/progress_circle_segment.underscore';
|
||||
|
||||
class ProgressCircleView extends Backbone.View {
|
||||
initialize() {
|
||||
this.x = 22;
|
||||
this.y = 22;
|
||||
this.radius = 16;
|
||||
this.degrees = 180;
|
||||
this.strokeWidth = 1.2;
|
||||
|
||||
this.viewTpl = _.template(progressViewTpl);
|
||||
this.segmentTpl = _.template(progressSegmentTpl);
|
||||
|
||||
const progress = this.model.get('progress');
|
||||
|
||||
this.model.set({
|
||||
totalCourses: progress.completed + progress.in_progress + progress.not_started,
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const 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));
|
||||
}
|
||||
|
||||
static getDegreeIncrement(total) {
|
||||
return 360 / total;
|
||||
}
|
||||
|
||||
static getOffset(total) {
|
||||
return 100 - ((1 / total) * 100);
|
||||
}
|
||||
|
||||
getProgressSegments() {
|
||||
const progressHTML = [];
|
||||
const total = this.model.get('totalCourses');
|
||||
const segmentDash = 2 * Math.PI * this.radius;
|
||||
const degreeInc = ProgressCircleView.getDegreeIncrement(total);
|
||||
const data = {
|
||||
// Remove strokeWidth to show a gap between the segments
|
||||
dashArray: segmentDash - this.strokeWidth,
|
||||
degrees: this.degrees,
|
||||
offset: ProgressCircleView.getOffset(total),
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
radius: this.radius,
|
||||
strokeWidth: this.strokeWidth,
|
||||
};
|
||||
|
||||
for (let i = 0; i < total; i += 1) {
|
||||
const 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('');
|
||||
}
|
||||
}
|
||||
|
||||
export default ProgressCircleView;
|
||||
@@ -1,41 +1,33 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
import _ from 'underscore';
|
||||
import Backbone from 'backbone';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'js/learner_dashboard/views/explore_new_programs_view',
|
||||
'text!../../../templates/learner_dashboard/sidebar.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
NewProgramsView,
|
||||
sidebarTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
el: '.sidebar',
|
||||
import NewProgramsView from './explore_new_programs_view';
|
||||
|
||||
tpl: _.template(sidebarTpl),
|
||||
import sidebarTpl from '../../../templates/learner_dashboard/sidebar.underscore';
|
||||
|
||||
initialize: function(data) {
|
||||
this.context = data.context;
|
||||
},
|
||||
class SidebarView extends Backbone.View {
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
el: '.sidebar',
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.tpl(this.context));
|
||||
this.postRender();
|
||||
},
|
||||
initialize(data) {
|
||||
this.tpl = _.template(sidebarTpl);
|
||||
this.context = data.context;
|
||||
}
|
||||
|
||||
postRender: function() {
|
||||
this.newProgramsView = new NewProgramsView({
|
||||
context: this.context
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
render() {
|
||||
this.$el.html(this.tpl(this.context));
|
||||
this.postRender();
|
||||
}
|
||||
|
||||
postRender() {
|
||||
this.newProgramsView = new NewProgramsView({
|
||||
context: this.context,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default SidebarView;
|
||||
|
||||
@@ -1,77 +1,72 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
el: '.unenroll-modal',
|
||||
/* globals gettext */
|
||||
|
||||
switchToSlideOne: function() {
|
||||
var survey, i;
|
||||
// Randomize survey option order
|
||||
survey = document.querySelector('.options');
|
||||
for (i = survey.children.length - 1; i >= 0; i--) {
|
||||
survey.appendChild(survey.children[Math.random() * i | 0]);
|
||||
}
|
||||
this.$('.inner-wrapper header').hide();
|
||||
this.$('#unenroll_form').hide();
|
||||
this.$('.slide1').removeClass('hidden');
|
||||
},
|
||||
import Backbone from 'backbone';
|
||||
|
||||
switchToSlideTwo: function() {
|
||||
var reason = this.$(".reasons_survey input[name='reason']:checked").attr('val');
|
||||
if (reason === 'Other') {
|
||||
reason = this.$('.other_text').val();
|
||||
}
|
||||
if (reason) {
|
||||
window.analytics.track('unenrollment_reason.selected', {
|
||||
category: 'user-engagement',
|
||||
label: reason,
|
||||
displayName: 'v1'
|
||||
});
|
||||
}
|
||||
this.$('.slide1').addClass('hidden');
|
||||
this.$('.survey_course_name').text(this.$('#unenroll_course_name').text());
|
||||
this.$('.slide2').removeClass('hidden');
|
||||
this.$('.reasons_survey .return_to_dashboard').attr('href', this.urls.dashboard);
|
||||
this.$('.reasons_survey .browse_courses').attr('href', this.urls.browseCourses);
|
||||
},
|
||||
class UnenrollView extends Backbone.View {
|
||||
|
||||
unenrollComplete: function(event, xhr) {
|
||||
if (xhr.status === 200) {
|
||||
if (!this.isEdx) {
|
||||
location.href = this.urls.dashboard;
|
||||
} else {
|
||||
this.switchToSlideOne();
|
||||
this.$('.reasons_survey:first .submit_reasons').click(this.switchToSlideTwo.bind(this));
|
||||
}
|
||||
} else if (xhr.status === 403) {
|
||||
location.href = this.urls.signInUser + '?course_id=' +
|
||||
encodeURIComponent($('#unenroll_course_id').val()) + '&enrollment_action=unenroll';
|
||||
} else {
|
||||
$('#unenroll_error').text(
|
||||
gettext('Unable to determine whether we should give you a refund because' +
|
||||
' of System Error. Please try again later.')
|
||||
).stop()
|
||||
.css('display', 'block');
|
||||
}
|
||||
},
|
||||
constructor(options) {
|
||||
const defaults = {
|
||||
el: '.unenroll-modal',
|
||||
};
|
||||
super(Object.assign({}, defaults, options));
|
||||
}
|
||||
|
||||
initialize: function(options) {
|
||||
this.urls = options.urls;
|
||||
this.isEdx = options.isEdx;
|
||||
switchToSlideOne() {
|
||||
// Randomize survey option order
|
||||
const survey = document.querySelector('.options');
|
||||
for (let i = survey.children.length - 1; i >= 0; i -= 1) {
|
||||
survey.appendChild(survey.children[Math.trunc(Math.random() * i)]);
|
||||
}
|
||||
this.$('.inner-wrapper header').hide();
|
||||
this.$('#unenroll_form').hide();
|
||||
this.$('.slide1').removeClass('hidden');
|
||||
}
|
||||
|
||||
$('#unenroll_form').on('ajax:complete', this.unenrollComplete.bind(this));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
switchToSlideTwo() {
|
||||
let reason = this.$(".reasons_survey input[name='reason']:checked").attr('val');
|
||||
if (reason === 'Other') {
|
||||
reason = this.$('.other_text').val();
|
||||
}
|
||||
if (reason) {
|
||||
window.analytics.track('unenrollment_reason.selected', {
|
||||
category: 'user-engagement',
|
||||
label: reason,
|
||||
displayName: 'v1',
|
||||
});
|
||||
}
|
||||
this.$('.slide1').addClass('hidden');
|
||||
this.$('.survey_course_name').text(this.$('#unenroll_course_name').text());
|
||||
this.$('.slide2').removeClass('hidden');
|
||||
this.$('.reasons_survey .return_to_dashboard').attr('href', this.urls.dashboard);
|
||||
this.$('.reasons_survey .browse_courses').attr('href', this.urls.browseCourses);
|
||||
}
|
||||
|
||||
unenrollComplete(event, xhr) {
|
||||
if (xhr.status === 200) {
|
||||
if (!this.isEdx) {
|
||||
location.href = this.urls.dashboard;
|
||||
} else {
|
||||
this.switchToSlideOne();
|
||||
this.$('.reasons_survey:first .submit_reasons').click(this.switchToSlideTwo.bind(this));
|
||||
}
|
||||
} else if (xhr.status === 403) {
|
||||
location.href = `${this.urls.signInUser}?course_id=${
|
||||
encodeURIComponent($('#unenroll_course_id').val())}&enrollment_action=unenroll`;
|
||||
} else {
|
||||
$('#unenroll_error').text(
|
||||
gettext('Unable to determine whether we should give you a refund because' +
|
||||
' of System Error. Please try again later.'),
|
||||
).stop()
|
||||
.css('display', 'block');
|
||||
}
|
||||
}
|
||||
|
||||
initialize(options) {
|
||||
this.urls = options.urls;
|
||||
this.isEdx = options.isEdx;
|
||||
|
||||
$('#unenroll_form').on('ajax:complete', this.unenrollComplete.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
export default UnenrollView;
|
||||
|
||||
@@ -1,34 +1,20 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/upgrade_message.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
upgradeMessageTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
messageTpl: HtmlUtils.template(upgradeMessageTpl),
|
||||
import Backbone from 'backbone';
|
||||
|
||||
initialize: function(options) {
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
},
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
render: function() {
|
||||
var data = this.model.toJSON();
|
||||
import upgradeMessageTpl from '../../../templates/learner_dashboard/upgrade_message.underscore';
|
||||
|
||||
HtmlUtils.setHtml(this.$el, this.messageTpl(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
class UpgradeMessageView extends Backbone.View {
|
||||
initialize(options) {
|
||||
this.messageTpl = HtmlUtils.template(upgradeMessageTpl);
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = this.model.toJSON();
|
||||
HtmlUtils.setHtml(this.$el, this.messageTpl(data));
|
||||
}
|
||||
}
|
||||
|
||||
export default UpgradeMessageView;
|
||||
|
||||
Reference in New Issue
Block a user