Commit d7b1f730 by Renzo Lucioni

Update CAT create/edit view to support creation and modification of credit courses

XCOM-511
parent faae1e32
...@@ -120,7 +120,7 @@ define([ ...@@ -120,7 +120,7 @@ define([
* *
* Note that audit seats cannot be created, only edited. * Note that audit seats cannot be created, only edited.
*/ */
creatableSeatTypes: ['honor', 'verified', 'professional'], creatableSeatTypes: ['honor', 'verified', 'professional', 'credit'],
initialize: function () { initialize: function () {
this.get('products').on('change:id_verification_required', this.triggerIdVerified, this); this.get('products').on('change:id_verification_required', this.triggerIdVerified, this);
...@@ -179,28 +179,30 @@ define([ ...@@ -179,28 +179,30 @@ define([
}, },
/** /**
* Returns an existing CourseSeat corresponding to the given seat type; or creates a new one, * Returns existing CourseSeats corresponding to the given seat type. If none
* if one is not found. * are not found, creates a new one.
* *
* @param {String} seatType * @param {String} seatType
* @returns {CourseSeat} * @returns {CourseSeat[]}
*/ */
getOrCreateSeat: function (seatType) { getOrCreateSeats: function (seatType) {
var seatClass, var seatClass,
seat = _.find(this.seats(), function (product) { seats = _.filter(this.seats(), function (product) {
// Find the seat with the specific seat type // Find the seats with the specific seat type
return product.getSeatType() === seatType; return product.getSeatType() === seatType;
}); }),
seat;
if (!seat && _.contains(this.creatableSeatTypes, seatType)) { if (_.isEmpty(seats) && _.contains(this.creatableSeatTypes, seatType)) {
seatClass = CourseUtils.getCourseSeatModel(seatType); seatClass = CourseUtils.getCourseSeatModel(seatType);
/*jshint newcap: false */ /*jshint newcap: false */
seat = new seatClass(); seat = new seatClass();
/*jshint newcap: true */ /*jshint newcap: true */
this.get('products').add(seat); this.get('products').add(seat);
seats.push(seat);
} }
return seat; return seats;
}, },
/** /**
......
...@@ -10,12 +10,15 @@ define([ ...@@ -10,12 +10,15 @@ define([
expires: null, expires: null,
id_verification_required: null, id_verification_required: null,
price: 0, price: 0,
credit_provider: null,
credit_hours: null,
product_class: 'Seat' product_class: 'Seat'
}, },
validation: { validation: {
price: { price: {
required: true, required: true,
pattern: 'number',
msg: gettext('All course seats must have a price.') msg: gettext('All course seats must have a price.')
}, },
product_class: { product_class: {
......
define([
'models/course_seats/course_seat'
],
function (CourseSeat) {
'use strict';
return CourseSeat.extend({
defaults: _.extend({}, CourseSeat.prototype.defaults,
{
certificate_type: 'credit',
id_verification_required: true,
price: 0,
credit_provider: null,
credit_hours: null
}
),
validation: _.extend({}, CourseSeat.prototype.validation,
{
credit_provider: {
required: true,
msg: gettext('All credit seats must have a credit provider.')
},
credit_hours: {
required: true,
pattern: 'number',
min: 0,
msg: gettext('All credit seats must designate a number of credit hours.')
}
}
)
}, {seatType: 'credit'});
}
);
...@@ -54,9 +54,9 @@ define([ ...@@ -54,9 +54,9 @@ define([
name: attribute, name: attribute,
value: this.get(attribute) value: this.get(attribute)
}); });
delete data[attribute];
} }
delete data[attribute];
}, this); }, this);
// Restore the timezone component, and output the ISO 8601 format expected by the server. // Restore the timezone component, and output the ISO 8601 format expected by the server.
......
...@@ -17,8 +17,8 @@ define([ ...@@ -17,8 +17,8 @@ define([
var model, var model,
honorSeat = { honorSeat = {
id: 9, id: 8,
url: 'http://ecommerce.local:8002/api/v2/products/9/', url: 'http://ecommerce.local:8002/api/v2/products/8/',
structure: 'child', structure: 'child',
product_class: 'Seat', product_class: 'Seat',
title: 'Seat in edX Demonstration Course with honor certificate', title: 'Seat in edX Demonstration Course with honor certificate',
...@@ -41,8 +41,8 @@ define([ ...@@ -41,8 +41,8 @@ define([
is_available_to_buy: true is_available_to_buy: true
}, },
verifiedSeat = { verifiedSeat = {
id: 8, id: 9,
url: 'http://ecommerce.local:8002/api/v2/products/8/', url: 'http://ecommerce.local:8002/api/v2/products/9/',
structure: 'child', structure: 'child',
product_class: 'Seat', product_class: 'Seat',
title: 'Seat in edX Demonstration Course with verified certificate (and ID verification)', title: 'Seat in edX Demonstration Course with verified certificate (and ID verification)',
...@@ -64,17 +64,83 @@ define([ ...@@ -64,17 +64,83 @@ define([
], ],
is_available_to_buy: true is_available_to_buy: true
}, },
creditSeat = {
id: 10,
url: 'http://ecommerce.local:8002/api/v2/products/10/',
structure: 'child',
product_class: 'Seat',
title: 'Seat in edX Demonstration Course with credit certificate (and ID verification)',
price: '200.00',
expires: null,
attribute_values: [
{
name: 'certificate_type',
value: 'credit'
},
{
name: 'course_key',
value: 'edX/DemoX/Demo_Course'
},
{
name: 'id_verification_required',
value: true
},
{
name: 'credit_provider',
value: 'Harvard'
},
{
name: 'credit_hours',
value: 1
}
],
is_available_to_buy: true
},
alternateCreditSeat = {
id: 11,
url: 'http://ecommerce.local:8002/api/v2/products/11/',
structure: 'child',
product_class: 'Seat',
title: 'Seat in edX Demonstration Course with credit certificate (and ID verification)',
price: '300.00',
expires: null,
attribute_values: [
{
name: 'certificate_type',
value: 'credit'
},
{
name: 'course_key',
value: 'edX/DemoX/Demo_Course'
},
{
name: 'id_verification_required',
value: true
},
{
name: 'credit_provider',
value: 'MIT'
},
{
name: 'credit_hours',
value: 2
}
],
is_available_to_buy: true
},
data = { data = {
id: 'edX/DemoX/Demo_Course', id: 'edX/DemoX/Demo_Course',
url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/', url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/',
name: 'edX Demonstration Course', name: 'edX Demonstration Course',
verification_deadline: '2015-10-01T00:00:00Z', verification_deadline: '2015-10-01T00:00:00Z',
type: 'verified', type: 'credit',
products_url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/products/', products_url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/products/',
last_edited: '2015-07-27T00:27:23Z', last_edited: '2015-07-27T00:27:23Z',
products: [ products: [
honorSeat, honorSeat,
verifiedSeat, verifiedSeat,
creditSeat,
alternateCreditSeat,
{ {
id: 7, id: 7,
url: 'http://ecommerce.local:8002/api/v2/products/7/', url: 'http://ecommerce.local:8002/api/v2/products/7/',
...@@ -111,14 +177,14 @@ define([ ...@@ -111,14 +177,14 @@ define([
// Sanity check to ensure the products were properly parsed // Sanity check to ensure the products were properly parsed
products = model.get('products'); products = model.get('products');
expect(products.length).toEqual(3); expect(products.length).toEqual(5);
// Remove the parent products // Remove the parent products
model.removeParentProducts(); model.removeParentProducts();
// Only the children survived... // Only the children survived...
expect(products.length).toEqual(2); expect(products.length).toEqual(4);
expect(products.where({structure: 'child'}).length).toEqual(2); expect(products.where({structure: 'child'}).length).toEqual(4);
}); });
}); });
...@@ -190,31 +256,37 @@ define([ ...@@ -190,31 +256,37 @@ define([
}); });
}); });
describe('getOrCreateSeat', function () { describe('getOrCreateSeats', function () {
it('should return existing seats', function () { it('should return existing seats', function () {
var mapping = { var mapping = {
'honor': honorSeat, 'honor': [honorSeat],
'verified': verifiedSeat 'verified': [verifiedSeat],
}; 'credit': [creditSeat, alternateCreditSeat]
},
seats;
_.each(mapping, function (expected, seatType) { _.each(mapping, function (expected, seatType) {
expect(model.getOrCreateSeat(seatType).toJSON()).toEqual(expected); seats = model.getOrCreateSeats(seatType);
_.each(seats, function (seat) {
expect(expected).toContain(seat.toJSON());
});
}); });
}); });
it('should return null if an audit seat does not already exist', function () { it('should return an empty array if an audit seat does not already exist', function () {
expect(model.getOrCreateSeat('audit')).toBeUndefined(); expect(model.getOrCreateSeats('audit')).toEqual([]);
}); });
it('should create a new CourseSeat if one does not exist', function () { it('should create a new CourseSeat if one does not exist', function () {
var seat; var seat;
// Sanity check to confirm a new seat is created later // Sanity check to confirm a new seat is created later
expect(model.seats().length).toEqual(2); expect(model.seats().length).toEqual(4);
// A new seat should be created // A new seat should be created
seat = model.getOrCreateSeat('professional'); seat = model.getOrCreateSeats('professional')[0];
expect(model.seats().length).toEqual(3); expect(model.seats().length).toEqual(5);
// The new seat's class/type should correspond to the passed in seat type // The new seat's class/type should correspond to the passed in seat type
expect(seat).toEqual(jasmine.any(ProfessionalSeat)); expect(seat).toEqual(jasmine.any(ProfessionalSeat));
...@@ -229,7 +301,7 @@ define([ ...@@ -229,7 +301,7 @@ define([
describe('verification deadline validation', function () { describe('verification deadline validation', function () {
it('succeeds if the verification deadline is after the course seats\' expiration dates', function () { it('succeeds if the verification deadline is after the course seats\' expiration dates', function () {
var seat = model.getOrCreateSeat('verified'); var seat = model.getOrCreateSeats('verified')[0];
model.set('verification_deadline', '2016-01-01T00:00:00Z'); model.set('verification_deadline', '2016-01-01T00:00:00Z');
seat.set('expires', '2015-01-01T00:00:00Z'); seat.set('expires', '2015-01-01T00:00:00Z');
...@@ -238,7 +310,7 @@ define([ ...@@ -238,7 +310,7 @@ define([
}); });
it('fails if the verification deadline is before the course seats\' expiration dates', function () { it('fails if the verification deadline is before the course seats\' expiration dates', function () {
var seat = model.getOrCreateSeat('verified'), var seat = model.getOrCreateSeats('verified')[0],
msg = 'The verification deadline must occur AFTER the upgrade deadline.'; msg = 'The verification deadline must occur AFTER the upgrade deadline.';
model.set('verification_deadline', '2014-01-01T00:00:00Z'); model.set('verification_deadline', '2014-01-01T00:00:00Z');
seat.set('expires', '2015-01-01T00:00:00Z'); seat.set('expires', '2015-01-01T00:00:00Z');
......
define([
'models/course_seats/professional_seat',
'views/course_seat_form_fields/professional_course_seat_form_field_view'
],
function (ProfessionalSeat,
CourseSeatFormFieldView) {
'use strict';
var model, view;
beforeEach(function () {
model = new ProfessionalSeat();
view = new CourseSeatFormFieldView({model: model}).render();
});
describe('professional course seat form field view', function () {
describe('getFieldValue', function () {
it('should return a boolean if the name is id_verification_required', function () {
// NOTE (CCB): Ideally _.each should be used here to loop over an array of Boolean values.
// However, the tests fail when that implementation is used, hence the repeated code.
model.set('id_verification_required', false);
expect(model.get('id_verification_required')).toEqual(false);
expect(view.getFieldValue('id_verification_required')).toEqual(false);
model.set('id_verification_required', true);
expect(model.get('id_verification_required')).toEqual(true);
expect(view.getFieldValue('id_verification_required')).toEqual(true);
});
// NOTE (CCB): This test is flaky (hence it being skipped).
// Occasionally, calls to the parent class fail.
xit('should always return professional if the name is certificate_type', function () {
expect(view.getFieldValue('certificate_type')).toEqual('professional');
});
});
});
}
);
define([
'models/course_seats/verified_seat',
'views/course_seat_form_fields/verified_course_seat_form_field_view'
],
function (VerifiedSeat,
VerifiedCourseSeatFormFieldView) {
'use strict';
var model, view;
beforeEach(function () {
model = new VerifiedSeat();
view = new VerifiedCourseSeatFormFieldView({model: model}).render();
});
describe('verified course seat form field view', function () {
describe('getData', function () {
it('should return the data from the DOM/model', function () {
var data = {
certificate_type: 'verified',
id_verification_required: 'true',
price: '100',
expires: ''
};
expect(view.getData()).toEqual(data);
});
});
});
}
);
...@@ -4,14 +4,16 @@ define([ ...@@ -4,14 +4,16 @@ define([
'models/course_seats/course_seat', 'models/course_seats/course_seat',
'models/course_seats/honor_seat', 'models/course_seats/honor_seat',
'models/course_seats/professional_seat', 'models/course_seats/professional_seat',
'models/course_seats/verified_seat' 'models/course_seats/verified_seat',
'models/course_seats/credit_seat'
], ],
function (_, function (_,
AuditSeat, AuditSeat,
CourseSeat, CourseSeat,
HonorSeat, HonorSeat,
ProfessionalSeat, ProfessionalSeat,
VerifiedSeat) { VerifiedSeat,
CreditSeat) {
'use strict'; 'use strict';
return { return {
...@@ -27,7 +29,7 @@ define([ ...@@ -27,7 +29,7 @@ define([
* @returns {CourseSeat[]} * @returns {CourseSeat[]}
*/ */
getSeatModelMap: _.memoize(function () { getSeatModelMap: _.memoize(function () {
return _.indexBy([AuditSeat, HonorSeat, ProfessionalSeat, VerifiedSeat], 'seatType'); return _.indexBy([AuditSeat, HonorSeat, ProfessionalSeat, VerifiedSeat, CreditSeat], 'seatType');
}), }),
/** /**
...@@ -74,7 +76,7 @@ define([ ...@@ -74,7 +76,7 @@ define([
return 'residual'; return 'residual';
}); });
}, }
}; };
} }
); );
define([ define([
'backbone',
'backbone.validation',
'moment', 'moment',
'underscore'], 'underscore'],
function (moment, function (Backbone,
BackboneValidation,
moment,
_) { _) {
'use strict'; 'use strict';
...@@ -81,6 +85,43 @@ define([ ...@@ -81,6 +85,43 @@ define([
return _.every(models, function (model) { return _.every(models, function (model) {
return model.isValid(true); return model.isValid(true);
}); });
},
/**
* Bind the provided view for form validation.
*
* @param {Backbone.View} view
*/
bindValidation: function (view) {
/* istanbul ignore next */
Backbone.Validation.bind(view, {
valid: function (view, attr) {
var $el = view.$el.find('[name=' + attr + ']'),
$group = $el.closest('.form-group'),
$helpBlock = $group.find('.help-block:first'),
className = 'invalid-' + attr,
$msg = $helpBlock.find('.' + className);
$msg.remove();
$group.removeClass('has-error');
$helpBlock.addClass('hidden');
},
invalid: function (view, attr, error) {
var $el = view.$el.find('[name=' + attr + ']'),
$group = $el.closest('.form-group'),
$helpBlock = $group.find('.help-block:first'),
className = 'invalid-' + attr,
$msg = $helpBlock.find('.' + className);
if (_.isEqual($msg.length, 0)) {
$helpBlock.append('<div class="' + className + '">' + error + '</div>');
}
$group.addClass('has-error');
$helpBlock.removeClass('hidden');
}
});
} }
}; };
} }
......
...@@ -9,12 +9,14 @@ define([ ...@@ -9,12 +9,14 @@ define([
'moment', 'moment',
'underscore', 'underscore',
'underscore.string', 'underscore.string',
'collections/product_collection',
'text!templates/course_form.html', 'text!templates/course_form.html',
'text!templates/_course_type_radio_field.html', 'text!templates/_course_type_radio_field.html',
'views/course_seat_form_fields/audit_course_seat_form_field_view', 'views/course_seat_form_fields/audit_course_seat_form_field_view',
'views/course_seat_form_fields/honor_course_seat_form_field_view', 'views/course_seat_form_fields/honor_course_seat_form_field_view',
'views/course_seat_form_fields/verified_course_seat_form_field_view', 'views/course_seat_form_fields/verified_course_seat_form_field_view',
'views/course_seat_form_fields/professional_course_seat_form_field_view', 'views/course_seat_form_fields/professional_course_seat_form_field_view',
'views/course_seat_form_fields/credit_course_seat_form_field_view',
'views/alert_view', 'views/alert_view',
'utils/course_utils' 'utils/course_utils'
], ],
...@@ -26,12 +28,14 @@ define([ ...@@ -26,12 +28,14 @@ define([
moment, moment,
_, _,
_s, _s,
ProductCollection,
CourseFormTemplate, CourseFormTemplate,
CourseTypeRadioTemplate, CourseTypeRadioTemplate,
AuditCourseSeatFormFieldView, AuditCourseSeatFormFieldView,
HonorCourseSeatFormFieldView, HonorCourseSeatFormFieldView,
VerifiedCourseSeatFormFieldView, VerifiedCourseSeatFormFieldView,
ProfessionalCourseSeatFormFieldView, ProfessionalCourseSeatFormFieldView,
CreditCourseSeatFormFieldView,
AlertView, AlertView,
CourseUtils) { CourseUtils) {
'use strict'; 'use strict';
...@@ -85,7 +89,7 @@ define([ ...@@ -85,7 +89,7 @@ define([
type: 'credit', type: 'credit',
displayName: gettext('Credit'), displayName: gettext('Credit'),
helpText: gettext('Paid certificate track with initial verification and Verified Certificate, ' + helpText: gettext('Paid certificate track with initial verification and Verified Certificate, ' +
'and option to buy credit') 'and option to purchase credit')
} }
}, },
...@@ -94,7 +98,8 @@ define([ ...@@ -94,7 +98,8 @@ define([
audit: AuditCourseSeatFormFieldView, audit: AuditCourseSeatFormFieldView,
honor: HonorCourseSeatFormFieldView, honor: HonorCourseSeatFormFieldView,
verified: VerifiedCourseSeatFormFieldView, verified: VerifiedCourseSeatFormFieldView,
professional: ProfessionalCourseSeatFormFieldView professional: ProfessionalCourseSeatFormFieldView,
credit: CreditCourseSeatFormFieldView
}, },
events: { events: {
...@@ -188,18 +193,11 @@ define([ ...@@ -188,18 +193,11 @@ define([
break; break;
} }
// TODO Activate credit seat
var index = activeCourseTypes.indexOf('credit');
if (index > -1) {
activeCourseTypes.splice(index, 1);
}
return activeCourseTypes; return activeCourseTypes;
}, },
setLockedCourseType: function () { setLockedCourseType: function () {
this.lockedCourseType = this.model.get('type'); this.lockedCourseType = this.model.get('type');
this.renderCourseTypes();
}, },
render: function () { render: function () {
...@@ -213,6 +211,9 @@ define([ ...@@ -213,6 +211,9 @@ define([
this.stickit(); this.stickit();
// Avoid the need to create this jQuery object every time an alert has to be rendered.
this.$alerts = this.$el.find('.alerts');
return this; return this;
}, },
...@@ -263,19 +264,25 @@ define([ ...@@ -263,19 +264,25 @@ define([
if (activeSeats.length < 1) { if (activeSeats.length < 1) {
activeSeats = ['empty']; activeSeats = ['empty'];
} else { } else {
_.each(CourseUtils.orderSeatTypesForDisplay(activeSeats), function (seatType) { _.each(CourseUtils.orderSeatTypesForDisplay(activeSeats), function (seatType) {
var model, var seats,
viewClass, viewClass,
view = this.courseSeatViews[seatType]; view = this.courseSeatViews[seatType];
if (!view) { if (!view) {
model = this.model.getOrCreateSeat(seatType); seats = this.model.getOrCreateSeats(seatType);
// seats = new ProductCollection(this.model.getOrCreateSeats(seatType));
viewClass = this.courseSeatViewMappings[seatType]; viewClass = this.courseSeatViewMappings[seatType];
if (viewClass && model) { if (viewClass && seats.length > 0) {
/*jshint newcap: false */ /*jshint newcap: false */
view = new viewClass({model: model}); if (_.isEqual(seatType, 'credit')) {
seats = new ProductCollection(seats);
view = new viewClass({collection: seats, course: this.model});
} else {
view = new viewClass({model: seats[0]});
}
/*jshint newcap: true */ /*jshint newcap: true */
view.render(); view.render();
...@@ -287,7 +294,7 @@ define([ ...@@ -287,7 +294,7 @@ define([
} }
// Retrieve these after any new renderings. // Retrieve these after any new renderings.
$courseSeats = $courseSeatsContainer.find('.course-seat'); $courseSeats = $courseSeatsContainer.find('.row');
// Hide all seats // Hide all seats
$courseSeats.hide(); $courseSeats.hide();
...@@ -307,10 +314,17 @@ define([ ...@@ -307,10 +314,17 @@ define([
*/ */
renderAlert: function (level, message) { renderAlert: function (level, message) {
var view = new AlertView({level: level, title: gettext('Error!'), message: message}); var view = new AlertView({level: level, title: gettext('Error!'), message: message});
view.render(); view.render();
this.$el.find('.alerts').append(view.el); this.$alerts.append(view.el);
this.alertViews.push(view); this.alertViews.push(view);
$('body').animate({
scrollTop: this.$alerts.offset().top
}, 500);
this.$alerts.focus();
return this; return this;
}, },
......
...@@ -4,14 +4,16 @@ define([ ...@@ -4,14 +4,16 @@ define([
'backbone.validation', 'backbone.validation',
'backbone.stickit', 'backbone.stickit',
'underscore', 'underscore',
'underscore.string' 'underscore.string',
'utils/utils'
], ],
function ($, function ($,
Backbone, Backbone,
BackboneValidation, BackboneValidation,
BackboneStickit, BackboneStickit,
_, _,
_s) { _s,
Utils) {
'use strict'; 'use strict';
return Backbone.View.extend({ return Backbone.View.extend({
...@@ -35,55 +37,22 @@ define([ ...@@ -35,55 +37,22 @@ define([
}, },
className: function () { className: function () {
return 'row course-seat ' + this.seatType; return 'row ' + this.seatType + ' course-seat';
}, },
initialize: function () { initialize: function () {
/* istanbul ignore next */ Utils.bindValidation(this);
Backbone.Validation.bind(this, {
valid: function (view, attr) {
var $el = view.$('[name=' + attr + ']'),
$group = $el.closest('.form-group');
$group.removeClass('has-error');
$group.find('.help-block:first').html('').addClass('hidden');
},
invalid: function (view, attr, error) {
var $el = view.$('[name=' + attr + ']'),
$group = $el.closest('.form-group');
$group.addClass('has-error');
$group.find('.help-block:first').html(error).removeClass('hidden');
}
});
}, },
render: function () { render: function () {
this.$el.html(this.template(this.model.attributes)); this.$el.html(this.template(this.model.attributes));
this.stickit(); this.stickit();
return this; return this;
}, },
cleanIdVerificationRequired: function (val) { cleanIdVerificationRequired: function (val) {
return _s.toBoolean(val); return _s.toBoolean(val);
},
getFieldValue: function (name) {
return this.$(_s.sprintf('input[name=%s]', name)).val();
},
/***
* Return the input data from the form fields.
*/
getData: function () {
var data = {},
fields = ['certificate_type', 'id_verification_required', 'price', 'expires'];
_.each(fields, function (field) {
data[field] = this.getFieldValue(field);
}, this);
return data;
} }
}); });
} }
......
define([
'backbone',
'backbone.stickit',
'text!templates/credit_course_seat_form_field_row.html'
],
function (Backbone,
BackboneStickit,
CreditSeatTableRowTemplate) {
'use strict';
return Backbone.View.extend({
tagName: 'tr',
className: 'course-seat',
template: _.template(CreditSeatTableRowTemplate),
events: {
'click .remove-seat': 'removeSeatTableRow'
},
bindings: {
'input[name=credit_provider]': {
observe: 'credit_provider',
setOptions: {
validate: true
}
},
'input[name=price]': {
observe: 'price',
setOptions: {
validate: true
}
},
'input[name=credit_hours]': {
observe: 'credit_hours',
setOptions: {
validate: true
}
},
'input[name=expires]': 'expires'
},
initialize: function (options) {
this.course = options.course;
this.isRemovable = options.isRemovable;
},
render: function () {
var context = _.extend({}, this.model.attributes, {isRemovable: this.isRemovable});
this.$el.html(this.template(context));
this.stickit();
return this;
},
/**
* Removes the selected row from the seat table.
*/
removeSeatTableRow: function () {
// Remove deleted seat from course product collection.
this.course.get('products').remove(this.model);
this.remove();
}
});
}
);
// jscs:disable requireCapitalizedConstructors
define([
'views/course_seat_form_fields/course_seat_form_field_view',
'views/course_seat_form_fields/credit_course_seat_form_field_row_view',
'text!templates/credit_course_seat_form_field.html',
'utils/course_utils',
'utils/utils'
],
function (CourseSeatFormFieldView,
CreditCourseSeatFormFieldRowView,
FieldTemplate,
CourseUtils,
Utils) {
'use strict';
return CourseSeatFormFieldView.extend({
certificateType: 'credit',
idVerificationRequired: true,
seatType: 'credit',
template: _.template(FieldTemplate),
rowView: CreditCourseSeatFormFieldRowView,
events: {
'click .add-seat': 'addSeatTableRow'
},
className: function () {
return 'row ' + this.seatType;
},
initialize: function (options) {
this.course = options.course;
Utils.bindValidation(this);
},
render: function () {
this.renderSeatTable();
return this;
},
/**
* Renders a table of course seats sharing a common seat type.
*/
renderSeatTable: function () {
var row,
$tableBody,
rows = [];
this.$el.html(this.template());
$tableBody = this.$el.find('tbody');
// Instantiate new Views handling data binding for each Model in the Collection.
this.collection.each( function (seat) {
row = new this.rowView({
model: seat,
isRemovable: false,
course: this.course
});
row.render();
rows.push(row.el);
}, this);
$tableBody.append(rows);
return this;
},
/**
* Adds a new row to the seat table.
*/
addSeatTableRow: function () {
var seatClass = CourseUtils.getCourseSeatModel(this.seatType),
/*jshint newcap: false */
seat = new seatClass(),
/*jshint newcap: true */
row = new this.rowView({
model: seat,
isRemovable: true,
course: this.course
}),
$tableBody = this.$el.find('tbody');
row.render();
$tableBody.append(row.el);
// Add new seat to course product collection.
this.course.get('products').add(seat);
}
});
}
);
define([ define([
'underscore.string',
'views/course_seat_form_fields/verified_course_seat_form_field_view', 'views/course_seat_form_fields/verified_course_seat_form_field_view',
'text!templates/professional_course_seat_form_field.html', 'text!templates/professional_course_seat_form_field.html'
'backbone.super'
], ],
function (_s, function (VerifiedCourseSeatFormFieldView,
VerifiedCourseSeatFormFieldView,
FieldTemplate) { FieldTemplate) {
'use strict'; 'use strict';
...@@ -13,20 +10,7 @@ define([ ...@@ -13,20 +10,7 @@ define([
certificateType: 'professional', certificateType: 'professional',
idVerificationRequired: false, idVerificationRequired: false,
seatType: 'professional', seatType: 'professional',
template: _.template(FieldTemplate), template: _.template(FieldTemplate)
getFieldValue: function (name) {
var value;
if (name === 'id_verification_required') {
value = this.$('input[name=id_verification_required]:checked').val();
value = _s.toBoolean(value);
} else {
value = this._super(name);
}
return value;
}
}); });
} }
); );
<div class="alerts"></div> <div class="alerts" tabindex="-1" aria-live="polite"></div>
<div class="fields"> <div class="fields">
<div class="form-group"> <div class="form-group">
......
<div class="col-sm-12">
<div class="seat-type"><%= gettext('Credit') %></div>
</div>
<div class="col-sm-4 col-sm-offset-4 seat-certificate-type">
<%= gettext('Verified Certificate') %>
</div>
<div class="col-md-10">
<input type="hidden" name="certificate_type" value="credit">
<input type="hidden" name="id_verification_required" value="true">
<div class="form-group">
<table class="table table-striped credit-seats">
<thead>
<tr>
<th id="credit-provider-label"><%= gettext('Credit Provider') %></th>
<th id="price-label"><%= gettext('Price (USD)') %></th>
<th id="credit-hours-label"><%= gettext('Credit Hours') %></th>
<th id="expires-label"><%= gettext('Upgrade Deadline') %></th>
<th>
<button class="add-seat btn btn-primary">
<span class="sr"><%= gettext('Add course seat') %></span>
<i class="fa fa-plus" aria-hidden="true"></i>
</button>
</th>
</tr>
</thead>
<tbody></tbody>
</table>
<!-- NOTE: This help-block is here for validation messages. -->
<span class="help-block" aria-live="polite"></span>
</div>
</div>
<div class="col-sm-4 seat-additional-info"></div>
<td class="credit-provider">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-building-o" aria-hidden="true"></i>
</div>
<input type="text" class="form-control" id="credit_provider" name="credit_provider"
aria-labelledby="credit-provider-label">
</div>
</td>
<td class="price">
<div class="input-group">
<div class="input-group-addon">$</div>
<input type="number" class="form-control" id="price" name="price" aria-labelledby="price-label"
min="5" step="1" pattern="\d+" value="<%= price %>">
</div>
</td>
<td class="credit-hours">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-clock-o" aria-hidden="true"></i>
</div>
<input type="number" class="form-control" id="credit_hours" name="credit_hours"
aria-labelledby="credit-hours-label" min="0" step="1" pattern="\d+">
</div>
</td>
<td class="expires">
<div class="input-group">
<div class="input-group-addon">
<i class="fa fa-calendar" aria-hidden="true"></i>
</div>
<input type="datetime-local" class="form-control" id="expires" name="expires"
aria-labelledby="expires-label">
</div>
</td>
<% if (isRemovable) { %>
<td>
<button class="remove-seat btn btn-danger">
<span class="sr"><%= gettext('Delete course seat') %></span>
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</td>
<% } %>
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