Commit e629ce2b by Will Daly

Move redirection/enrollment to AccessView and trigger it using an event.

Ensure window.isExternal is loaded

Don't set a default for terms of service

For paid courses, add the course to the cart and redirect to the shopping cart view.

Don't send form method and url as form data.

Stub window.analytics in the access view test.
parent 1a5cc867
......@@ -14,7 +14,7 @@ from student.models import NonExistentCourseError, CourseEnrollmentException
class EnrollmentUserThrottle(UserRateThrottle):
rate = '50/second' # TODO Limit significantly after performance testing.
rate = '50/second' # TODO Limit significantly after performance testing.
@api_view(['GET'])
......
......@@ -1034,7 +1034,9 @@ instructor_dash_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/ins
student_account_js = [
'js/utils/rwd_header_footer.js',
'js/utils/edx.utils.validate.js',
'js/student_account/enrollment_interface.js',
'js/src/utility.js',
'js/student_account/enrollment.js',
'js/student_account/shoppingcart.js',
'js/student_account/models/LoginModel.js',
'js/student_account/models/RegisterModel.js',
'js/student_account/models/PasswordResetModel.js',
......
......@@ -289,9 +289,13 @@
exports: 'NotificationView',
deps: ['backbone', 'jquery', 'underscore']
},
'js/student_account/enrollment_interface': {
'js/student_account/enrollment': {
exports: 'edx.student.account.EnrollmentInterface',
deps: ['jquery', 'jquery.cookie', 'underscore', 'gettext']
deps: ['jquery', 'jquery.cookie']
},
'js/student_account/shoppingcart': {
exports: 'edx.student.account.ShoppingCartInterface',
deps: ['jquery', 'jquery.cookie', 'underscore']
},
// Student account registration/login
// Loaded explicitly until these are converted to RequireJS
......@@ -350,13 +354,16 @@
'underscore',
'backbone',
'gettext',
'utility',
'js/student_account/views/LoginView',
'js/student_account/views/PasswordResetView',
'js/student_account/views/RegisterView',
'js/student_account/models/LoginModel',
'js/student_account/models/PasswordResetModel',
'js/student_account/models/RegisterModel',
'js/student_account/views/FormView'
'js/student_account/views/FormView',
'js/student_account/enrollment',
'js/student_account/shoppingcart',
]
}
}
......@@ -375,7 +382,8 @@
'lms/include/js/spec/student_account/login_spec.js',
'lms/include/js/spec/student_account/register_spec.js',
'lms/include/js/spec/student_account/password_reset_spec.js',
'lms/include/js/spec/student_account/enrollment_interface_spec.js',
'lms/include/js/spec/student_account/enrollment_spec.js',
'lms/include/js/spec/student_account/shoppingcart_spec.js',
'lms/include/js/spec/student_profile/profile_spec.js'
]);
......
......@@ -3,8 +3,10 @@ define([
'js/common_helpers/template_helpers',
'js/common_helpers/ajax_helpers',
'js/student_account/views/AccessView',
'js/student_account/views/FormView'
], function($, TemplateHelpers, AjaxHelpers, AccessView) {
'js/student_account/views/FormView',
'js/student_account/enrollment',
'js/student_account/shoppingcart'
], function($, TemplateHelpers, AjaxHelpers, AccessView, FormView, EnrollmentInterface, ShoppingCartInterface) {
describe('edx.student.account.AccessView', function() {
'use strict';
......@@ -51,7 +53,9 @@ define([
}
}
]
};
},
FORWARD_URL = '/courseware/next',
COURSE_KEY = 'edx/DemoX/Fall';
var ajaxAssertAndRespond = function(url, requestIndex) {
// Verify that the client contacts the server as expected
......@@ -77,6 +81,14 @@ define([
platformName: 'edX'
});
// Mock the redirect call
spyOn( view, 'redirect' ).andCallFake( function() {} );
// Mock the enrollment and shopping cart interfaces
spyOn( EnrollmentInterface, 'enroll' ).andCallFake( function() {} );
spyOn( ShoppingCartInterface, 'addCourseToCart' ).andCallFake( function() {} );
// Initialize the subview
ajaxAssertAndRespond(AJAX_INFO[mode].url);
};
......@@ -97,6 +109,20 @@ define([
ajaxAssertAndRespond(AJAX_INFO[type].url, AJAX_INFO[type].requestIndex);
};
/**
* Simulate query string params.
*
* @param {object} params Parameters to set, each of which
* should be prefixed with '?'
*/
var setFakeQueryParams = function( params ) {
spyOn( $, 'url' ).andCallFake(function( requestedParam ) {
if ( params.hasOwnProperty(requestedParam) ) {
return params[requestedParam];
}
});
};
beforeEach(function() {
setFixtures('<div id="login-and-registration-container"></div>');
TemplateHelpers.installTemplate('templates/student_account/access');
......@@ -104,6 +130,11 @@ define([
TemplateHelpers.installTemplate('templates/student_account/register');
TemplateHelpers.installTemplate('templates/student_account/password_reset');
TemplateHelpers.installTemplate('templates/student_account/form_field');
// Stub analytics tracking
// TODO: use RequireJS to ensure that this is loaded correctly
window.analytics = window.analytics || {};
window.analytics.track = window.analytics.track || function() {};
});
it('can initially display the login form', function() {
......@@ -143,14 +174,83 @@ define([
view.resetPassword();
ajaxAssertAndRespond(
AJAX_INFO['password_reset'].url,
AJAX_INFO['password_reset'].requestIndex
AJAX_INFO.password_reset.url,
AJAX_INFO.password_reset.requestIndex
);
// Verify that the password reset wrapper is populated
expect($('#password-reset-wrapper')).not.toBeEmpty();
});
it('enrolls the user on auth complete', function() {
ajaxSpyAndInitialize(this, 'login');
// Simulate providing enrollment query string params
setFakeQueryParams({
'?enrollment_action': 'enroll',
'?course_id': COURSE_KEY
});
// Trigger auth complete on the login view
view.subview.login.trigger('auth-complete');
// Expect that the view tried to enroll the student
expect( EnrollmentInterface.enroll ).toHaveBeenCalledWith( COURSE_KEY );
});
it('adds a white-label course to the shopping cart on auth complete', function() {
ajaxSpyAndInitialize(this, 'register');
// Simulate providing "add to cart" query string params
setFakeQueryParams({
'?enrollment_action': 'add_to_cart',
'?course_id': COURSE_KEY
});
// Trigger auth complete on the register view
view.subview.register.trigger('auth-complete');
// Expect that the view tried to add the course to the user's shopping cart
expect( ShoppingCartInterface.addCourseToCart ).toHaveBeenCalledWith( COURSE_KEY );
});
it('redirects the user to the dashboard on auth complete', function() {
ajaxSpyAndInitialize(this, 'register');
// Trigger auth complete
view.subview.register.trigger('auth-complete');
// Since we did not provide a ?next query param, expect a redirect to the dashboard.
expect( view.redirect ).toHaveBeenCalledWith( '/dashboard' );
});
it('redirects the user to the next page on auth complete', function() {
ajaxSpyAndInitialize(this, 'register');
// Simulate providing a ?next query string parameter
setFakeQueryParams({ '?next': FORWARD_URL });
// Trigger auth complete
view.subview.register.trigger('auth-complete');
// Verify that we were redirected
expect( view.redirect ).toHaveBeenCalledWith( FORWARD_URL );
});
it('ignores redirect to external URLs', function() {
ajaxSpyAndInitialize(this, 'register');
// Simulate providing a ?next query string parameter
// that goes to an external URL
setFakeQueryParams({ '?next': "http://www.example.com" });
// Trigger auth complete
view.subview.register.trigger('auth-complete');
// Expect that we ignore the external URL and redirect to the dashboard
expect( view.redirect ).toHaveBeenCalledWith( "/dashboard" );
});
it('displays an error if a form definition could not be loaded', function() {
// Spy on AJAX requests
requests = AjaxHelpers.requests(this);
......
define(['js/student_account/enrollment_interface'],
function(EnrollmentInterface) {
describe("edx.student.account.EnrollmentInterface", function() {
'use strict';
it('checks if a given course mode slug exists in an array of mode objects', function() {
var courseModes = [ { slug: 'honor' }, { slug: 'professional' } ]
expect( EnrollmentInterface.modeInArray( courseModes, 'professional' ) ).toBe(true);
expect( EnrollmentInterface.modeInArray( courseModes, 'audit' ) ).toBe(false);
});
});
}
);
define(['js/common_helpers/ajax_helpers', 'js/student_account/enrollment'],
function( AjaxHelpers, EnrollmentInterface ) {
'use strict';
describe( 'edx.student.account.EnrollmentInterface', function() {
var COURSE_KEY = 'edX/DemoX/Fall',
ENROLL_URL = '/enrollment/v0/course/edX/DemoX/Fall',
FORWARD_URL = '/course_modes/choose/edX/DemoX/Fall/';
beforeEach(function() {
// Mock the redirect call
spyOn(EnrollmentInterface, 'redirect').andCallFake(function() {});
});
it('enrolls a user in a course', function() {
// Spy on Ajax requests
var requests = AjaxHelpers.requests( this );
// Attempt to enroll the user
EnrollmentInterface.enroll( COURSE_KEY );
// Expect that the correct request was made to the server
AjaxHelpers.expectRequest( requests, 'POST', ENROLL_URL );
// Simulate a successful response from the server
AjaxHelpers.respondWithJson(requests, {});
// Verify that the user was redirected correctly
expect( EnrollmentInterface.redirect ).toHaveBeenCalledWith( FORWARD_URL );
});
it('redirects the user if enrollment fails', function() {
// Spy on Ajax requests
var requests = AjaxHelpers.requests( this );
// Attempt to enroll the user
EnrollmentInterface.enroll( COURSE_KEY );
// Simulate an error response from the server
AjaxHelpers.respondWithError(requests);
// Verify that the user was still redirected
expect(EnrollmentInterface.redirect).toHaveBeenCalledWith( FORWARD_URL );
});
});
}
);
......@@ -12,6 +12,7 @@ define([
var model = null,
view = null,
requests = null,
authComplete = false,
PLATFORM_NAME = 'edX',
USER_DATA = {
email: 'xsy@edx.org',
......@@ -72,7 +73,10 @@ define([
var createLoginView = function(test) {
// Initialize the login model
model = new LoginModel({ url: FORM_DESCRIPTION.submit_url });
model = new LoginModel({}, {
url: FORM_DESCRIPTION.submit_url,
method: FORM_DESCRIPTION.method
});
// Initialize the login view
view = new LoginView({
......@@ -85,9 +89,10 @@ define([
// Spy on AJAX requests
requests = AjaxHelpers.requests(test);
// Mock out redirection logic
spyOn(view, 'redirect').andCallFake(function() {
return true;
// Intercept events from the view
authComplete = false;
view.on("auth-complete", function() {
authComplete = true;
});
};
......@@ -130,16 +135,16 @@ define([
// Verify that the client contacts the server with the expected data
AjaxHelpers.expectRequest(
requests, 'POST', FORM_DESCRIPTION.submit_url, $.param(
$.extend({url: FORM_DESCRIPTION.submit_url}, USER_DATA)
)
requests, 'POST',
FORM_DESCRIPTION.submit_url,
$.param( USER_DATA )
);
// Respond with status code 200
AjaxHelpers.respondWithJson(requests, {});
// Verify that the user is redirected to the dashboard
expect(view.redirect).toHaveBeenCalledWith('/dashboard');
// Verify that auth-complete is triggered
expect(authComplete).toBe(true);
});
it('displays third-party auth login buttons', function() {
......@@ -175,6 +180,9 @@ define([
// Verify that submission errors are visible
expect(view.$errors).not.toHaveClass('hidden');
// Expect auth complete NOT to have been triggered
expect(authComplete).toBe(false);
});
it('displays an error if the server returns an error while logging in', function() {
......@@ -186,9 +194,9 @@ define([
// Simulate an error from the LMS servers
AjaxHelpers.respondWithError(requests);
// Expect that an error is displayed, and that we haven't been redirected
// Expect that an error is displayed and that auth complete is not triggered
expect(view.$errors).not.toHaveClass('hidden');
expect(view.redirect).not.toHaveBeenCalled();
expect(authComplete).toBe(false);
// If we try again and succeed, the error should go away
submitForm();
......@@ -196,8 +204,9 @@ define([
// This time, respond with status code 200
AjaxHelpers.respondWithJson(requests, {});
// Expect that the error is hidden
// Expect that the error is hidden and auth complete is triggered
expect(view.$errors).toHaveClass('hidden');
expect(authComplete).toBe(true);
});
});
}
......
......@@ -30,7 +30,10 @@ define([
var createPasswordResetView = function(that) {
// Initialize the password reset model
model = new PasswordResetModel({ url: FORM_DESCRIPTION.submit_url });
model = new PasswordResetModel({}, {
url: FORM_DESCRIPTION.submit_url,
method: FORM_DESCRIPTION.method
});
// Initialize the password reset view
view = new PasswordResetView({
......@@ -77,10 +80,9 @@ define([
// Verify that the client contacts the server with the expected data
AjaxHelpers.expectRequest(
requests, 'POST', FORM_DESCRIPTION.submit_url, $.param({
url: FORM_DESCRIPTION.submit_url,
email: EMAIL
})
requests, 'POST',
FORM_DESCRIPTION.submit_url,
$.param({ email: EMAIL })
);
// Respond with status code 200
......@@ -125,10 +127,10 @@ define([
// If we try again and succeed, the error should go away
submitEmail();
// This time, respond with status code 200
AjaxHelpers.respondWithJson(requests, {});
// Expect that the error is hidden
expect(view.$errors).toHaveClass('hidden');
});
......
......@@ -12,6 +12,7 @@ define([
var model = null,
view = null,
requests = null,
authComplete = false,
PLATFORM_NAME = 'edX',
USER_DATA = {
email: 'xsy@edx.org',
......@@ -160,7 +161,10 @@ define([
var createRegisterView = function(that) {
// Initialize the register model
model = new RegisterModel({ url: FORM_DESCRIPTION.submit_url });
model = new RegisterModel({}, {
url: FORM_DESCRIPTION.submit_url,
method: FORM_DESCRIPTION.method
});
// Initialize the register view
view = new RegisterView({
......@@ -173,9 +177,10 @@ define([
// Spy on AJAX requests
requests = AjaxHelpers.requests(that);
// Mock out redirection logic
spyOn(view, 'redirect').andCallFake(function() {
return true;
// Intercept events from the view
authComplete = false;
view.on("auth-complete", function() {
authComplete = true;
});
};
......@@ -225,16 +230,16 @@ define([
// Verify that the client contacts the server with the expected data
AjaxHelpers.expectRequest(
requests, 'POST', FORM_DESCRIPTION.submit_url, $.param(
$.extend({url: FORM_DESCRIPTION.submit_url}, USER_DATA)
)
requests, 'POST',
FORM_DESCRIPTION.submit_url,
$.param( USER_DATA )
);
// Respond with status code 200
AjaxHelpers.respondWithJson(requests, {});
// Verify that the user is redirected to the dashboard
expect(view.redirect).toHaveBeenCalledWith('/dashboard');
// Verify that auth complete is triggered
expect(authComplete).toBe(true);
});
it('displays third-party auth registration buttons', function() {
......@@ -269,6 +274,9 @@ define([
// Verify that submission errors are visible
expect(view.$errors).not.toHaveClass('hidden');
// Expect that auth complete is NOT triggered
expect(authComplete).toBe(false);
});
it('displays an error if the server returns an error while registering', function() {
......@@ -280,8 +288,9 @@ define([
// Simulate an error from the LMS servers
AjaxHelpers.respondWithError(requests);
// Expect that an error is displayed
// Expect that an error is displayed and that auth complete is NOT triggered
expect(view.$errors).not.toHaveClass('hidden');
expect(authComplete).toBe(false);
// If we try again and succeed, the error should go away
submitForm();
......@@ -289,8 +298,9 @@ define([
// This time, respond with status code 200
AjaxHelpers.respondWithJson(requests, {});
// Expect that the error is hidden
// Expect that the error is hidden and that auth complete is triggered
expect(view.$errors).toHaveClass('hidden');
expect(authComplete).toBe(true);
});
});
}
......
define(['js/common_helpers/ajax_helpers', 'js/student_account/shoppingcart'],
function(AjaxHelpers, ShoppingCartInterface) {
'use strict';
describe( 'edx.student.account.ShoppingCartInterface', function() {
var COURSE_KEY = "edX/DemoX/Fall",
ADD_COURSE_URL = "/shoppingcart/add/course/edX/DemoX/Fall/",
FORWARD_URL = "/shoppingcart/";
beforeEach(function() {
// Mock the redirect call
spyOn(ShoppingCartInterface, 'redirect').andCallFake(function() {});
});
it('adds a course to the cart', function() {
// Spy on Ajax requests
var requests = AjaxHelpers.requests( this );
// Attempt to add a course to the cart
ShoppingCartInterface.addCourseToCart( COURSE_KEY );
// Expect that the correct request was made to the server
AjaxHelpers.expectRequest( requests, 'POST', ADD_COURSE_URL );
// Simulate a successful response from the server
AjaxHelpers.respondWithJson( requests, {} );
// Expect that the user was redirected to the shopping cart
expect( ShoppingCartInterface.redirect ).toHaveBeenCalledWith( FORWARD_URL );
});
it('redirects the user on a server error', function() {
// Spy on Ajax requests
var requests = AjaxHelpers.requests( this );
// Attempt to add a course to the cart
ShoppingCartInterface.addCourseToCart( COURSE_KEY );
// Simulate an error response from the server
AjaxHelpers.respondWithError( requests );
// Expect that the user was redirected to the shopping cart
expect( ShoppingCartInterface.redirect ).toHaveBeenCalledWith( FORWARD_URL );
});
});
}
);
var edx = edx || {};
(function($) {
'use strict';
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.EnrollmentInterface = {
urls: {
course: '/enrollment/v0/course/',
trackSelection: '/course_modes/choose/'
},
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
/**
* Enroll a user in a course, then redirect the user
* to the track selection page.
* @param {string} courseKey Slash-separated course key.
*/
enroll: function( courseKey ) {
$.ajax({
url: this.courseEnrollmentUrl( courseKey ),
type: 'POST',
data: {},
headers: this.headers,
context: this
}).always(function() {
this.redirect( this.trackSelectionUrl( courseKey ) );
});
},
/**
* Construct the URL to the track selection page for a course.
* @param {string} courseKey Slash-separated course key.
* @return {string} The URL to the track selection page.
*/
trackSelectionUrl: function( courseKey ) {
return this.urls.trackSelection + courseKey + '/';
},
/**
* Construct a URL to enroll in a course.
* @param {string} courseKey Slash-separated course key.
* @return {string} The URL to enroll in a course.
*/
courseEnrollmentUrl: function( courseKey ) {
return this.urls.course + courseKey;
},
/**
* Redirect to a URL. Mainly useful for mocking out in tests.
* @param {string} url The URL to redirect to.
*/
redirect: function(url) {
window.location.href = url;
}
};
})(jQuery);
var edx = edx || {};
(function($, _, gettext) {
'use strict';
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.EnrollmentInterface = {
courseUrl: '/enrollment/v0/course/',
studentUrl: '/enrollment/v0/student',
trackSelectionUrl: '/course_modes/choose/',
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
studentInformation: function(courseKey) {
// retrieve student enrollment information
},
courseInformation: function(courseKey) {
// retrieve course information from the enrollment API
},
modeInArray: function(modeObjects, targetMode) {
// Check if a given course mode slug exists in an array of mode objects
var result = _.find(modeObjects, function(mode) {
return mode.slug === targetMode;
});
/* _.find returns the first value which passes the provided truth test,
/* or undefined if no values pass the test
*/
return !_.isUndefined(result);
},
enroll: function(courseKey, forwardUrl){
var me = this;
// attempt to enroll a student in a course
$.ajax({
url: this.courseUrl + courseKey,
type: 'POST',
data: {},
headers: this.headers
}).done(function(data){
me.postEnrollmentHandler(courseKey, data, forwardUrl);
}
).fail(function(data, textStatus) {
me.enrollmentFailureHandler(courseKey, data, forwardUrl);
});
},
enrollmentFailureHandler: function(courseKey, data, forwardUrl) {
// handle failures to enroll via the API
if(data.status == 400) {
/* This status code probably means we don't have permissions to register
/* for this course; look at the contents of the response
*/
var course = $.parseJSON(data.responseText);
// see if it's a professional ed course
if( 'course_modes' in course && this.modeInArray(course.course_modes, 'professional') ) {
// forward appropriately
forwardUrl = this.trackSelectionUrl + courseKey;
}
}
// TODO: if we have a paid registration mode, add item to the cart and send them along
// TODO: we should figure out how to handle errors here
window.location.href = forwardUrl;
},
postEnrollmentHandler: function(courseKey, data, forwardUrl) {
// Determine whether or not the course needs to be redirected to
// a particular page.
var course = data.course,
course_modes = course.course_modes;
// send the user to the track selection page, because it will do the right thing
forwardUrl = this.trackSelectionUrl + courseKey;
window.location.href = forwardUrl;
}
};
})(jQuery, _, gettext);
......@@ -18,9 +18,9 @@ var edx = edx || {};
urlRoot: '',
initialize: function( obj ) {
this.ajaxType = obj.method;
this.urlRoot = obj.url;
initialize: function( attributes, options ) {
this.ajaxType = options.method;
this.urlRoot = options.url;
},
sync: function(method, model) {
......
......@@ -16,9 +16,9 @@ var edx = edx || {};
urlRoot: '',
initialize: function( obj ) {
this.ajaxType = obj.method;
this.urlRoot = obj.url;
initialize: function( attributes, options ) {
this.ajaxType = options.method;
this.urlRoot = options.url;
},
sync: function(method, model) {
......
......@@ -18,16 +18,15 @@ var edx = edx || {};
year_of_birth: '',
mailing_address: '',
goals: '',
honor_code: false
},
ajaxType: '',
urlRoot: '',
initialize: function( obj ) {
this.ajaxType = obj.method;
this.urlRoot = obj.url;
initialize: function( attributes, options ) {
this.ajaxType = options.method;
this.urlRoot = options.url;
},
sync: function(method, model) {
......
/**
* Use the shopping cart to purchase courses.
*/
var edx = edx || {};
(function($) {
'use strict';
edx.student = edx.student || {};
edx.student.account = edx.student.account || {};
edx.student.account.ShoppingCartInterface = {
urls: {
viewCart: "/shoppingcart/",
addCourse: "/shoppingcart/add/course/"
},
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
/**
* Add a course to a cart, then redirect to the view cart page.
* @param {string} courseId The slash-separated course ID to add to the cart.
*/
addCourseToCart: function( courseId ) {
$.ajax({
url: this.urls.addCourse + courseId + "/",
type: 'POST',
data: {},
headers: this.headers,
context: this
}).always(function() {
this.redirect( this.urls.viewCart );
});
},
/**
* Redirect to a URL. Mainly useful for mocking out in tests.
* @param {string} url The URL to redirect to.
*/
redirect: function( url ) {
window.location.href = url;
}
};
})(jQuery);
......@@ -64,7 +64,7 @@ var edx = edx || {};
load: {
login: function( data, context ) {
var model = new edx.student.account.LoginModel({
var model = new edx.student.account.LoginModel({}, {
method: data.method,
url: data.submit_url
});
......@@ -78,10 +78,14 @@ var edx = edx || {};
// Listen for 'password-help' event to toggle sub-views
context.listenTo( context.subview.login, 'password-help', context.resetPassword );
// Listen for 'auth-complete' event so we can enroll/redirect the user appropriately.
context.listenTo( context.subview.login, 'auth-complete', context.authComplete );
},
reset: function( data, context ) {
var model = new edx.student.account.PasswordResetModel({
var model = new edx.student.account.PasswordResetModel({}, {
method: data.method,
url: data.submit_url
});
......@@ -93,7 +97,7 @@ var edx = edx || {};
},
register: function( data, context ) {
var model = new edx.student.account.RegisterModel({
var model = new edx.student.account.RegisterModel({}, {
method: data.method,
url: data.submit_url
});
......@@ -104,6 +108,9 @@ var edx = edx || {};
thirdPartyAuth: context.thirdPartyAuth,
platformName: context.platformName
});
// Listen for 'auth-complete' event so we can enroll/redirect the user appropriately.
context.listenTo( context.subview.register, 'auth-complete', context.authComplete );
}
},
......@@ -162,6 +169,92 @@ var edx = edx || {};
},'slow');
},
/**
* Once authentication has completed successfully, a user may need to:
*
* - Enroll in a course.
* - Add a course to the shopping cart.
* - Be redirected to the dashboard / track selection page / shopping cart.
*
* This handler is triggered upon successful authentication,
* either from the login or registration form. It checks
* query string params, performs enrollment/shopping cart actions,
* then redirects the user to the next page.
*
* The optional query string params are:
*
* ?next: If provided, redirect to this page upon successful auth.
* Django uses this when an unauthenticated user accesses a view
* decorated with @login_required.
*
* ?enrollment_action: Can be either "enroll" or "add_to_cart".
* If you provide this param, you must also provide a `course_id` param;
* otherwise, no action will be taken.
*
* ?course_id: The slash-separated course ID to enroll in or add to the cart.
*
*/
authComplete: function() {
var enrollment = edx.student.account.EnrollmentInterface,
shoppingcart = edx.student.account.ShoppingCartInterface,
redirectUrl = '/dashboard',
queryParams = this.queryParams();
if ( queryParams.enrollmentAction === 'enroll' && queryParams.courseId) {
/*
If we need to enroll in a course, mark as enrolled.
The enrollment interface will redirect the student once enrollment completes.
*/
enrollment.enroll( decodeURIComponent( queryParams.courseId ) );
} else if ( queryParams.enrollmentAction === 'add_to_cart' && queryParams.courseId) {
/*
If this is a paid course, add it to the shopping cart and redirect
the user to the "view cart" page.
*/
shoppingcart.addCourseToCart( decodeURIComponent( queryParams.courseId ) );
} else {
/*
Otherwise, redirect the user to the next page
Check for forwarding url and ensure that it isn't external.
If not, use the default forwarding URL.
*/
if ( !_.isNull( queryParams.next ) ) {
var next = decodeURIComponent( queryParams.next );
// Ensure that the URL is internal for security reasons
if ( !window.isExternal( next ) ) {
redirectUrl = next;
}
}
this.redirect( redirectUrl );
}
},
/**
* Redirect to a URL. Mainly useful for mocking out in tests.
* @param {string} url The URL to redirect to.
*/
redirect: function( url ) {
window.location.href = url;
},
/**
* Retrieve query params that we use post-authentication
* to decide whether to enroll a student in a course, add
* an item to the cart, or redirect.
*
* @return {object} The query params. If any param is not
* provided, it will default to null.
*/
queryParams: function() {
return {
next: $.url( '?next' ),
enrollmentAction: $.url( '?enrollment_action' ),
courseId: $.url( '?course_id' )
};
},
form: {
isLoaded: function( $form ) {
return $form.html().length > 0;
......
......@@ -79,29 +79,7 @@ var edx = edx || {};
},
saveSuccess: function () {
var enrollment = edx.student.account.EnrollmentInterface,
redirectUrl = '/dashboard',
next = null;
// Check for forwarding url
if ( !_.isNull( $.url('?next') ) ) {
next = decodeURIComponent( $.url('?next') );
if ( !window.isExternal(next) ) {
redirectUrl = next;
}
}
// If we need to enroll in a course, mark as enrolled
if ( $.url('?enrollment_action') === 'enroll' ) {
enrollment.enroll( decodeURIComponent( $.url('?course_id') ), redirectUrl );
} else {
this.redirect(redirectUrl);
}
},
redirect: function( url ) {
window.location.href = url;
this.trigger('auth-complete');
},
saveError: function( error ) {
......
var edx = edx || {};
(function($, _, gettext) {
(function($, gettext) {
'use strict';
edx.student = edx.student || {};
......@@ -39,4 +39,4 @@ var edx = edx || {};
}
});
})(jQuery, _, gettext);
})(jQuery, gettext);
......@@ -55,25 +55,7 @@ var edx = edx || {};
},
saveSuccess: function() {
var enrollment = edx.student.account.EnrollmentInterface,
redirectUrl = '/dashboard',
next = null;
// Check for forwarding url
if ( !_.isNull( $.url('?next') ) ) {
next = decodeURIComponent( $.url('?next') );
if ( !window.isExternal(next) ) {
redirectUrl = next;
}
}
// If we need to enroll in a course, mark as enrolled
if ( $.url('?enrollment_action') === 'enroll' ) {
enrollment.enroll( decodeURIComponent( $.url('?course_id') ), redirectUrl );
} else {
this.redirect(redirectUrl);
}
this.trigger('auth-complete');
},
redirect: function( url ) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment