Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
ecommerce
Commits
d7b1f730
Commit
d7b1f730
authored
Aug 18, 2015
by
Renzo Lucioni
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update CAT create/edit view to support creation and modification of credit courses
XCOM-511
parent
faae1e32
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
609 additions
and
236 deletions
+609
-236
ecommerce/static/js/models/course_model.js
+12
-10
ecommerce/static/js/models/course_seats/course_seat.js
+3
-0
ecommerce/static/js/models/course_seats/credit_seat.js
+34
-0
ecommerce/static/js/models/product_model.js
+2
-2
ecommerce/static/js/test/specs/models/course_model_spec.js
+92
-20
ecommerce/static/js/test/specs/views/course_edit_view_spec.js
+138
-56
ecommerce/static/js/test/specs/views/course_seat_form_fields/professional_course_seat_form_field_view_spec.js
+0
-38
ecommerce/static/js/test/specs/views/course_seat_form_fields/verified_seat_form_field_view_spec.js
+0
-31
ecommerce/static/js/utils/course_utils.js
+6
-4
ecommerce/static/js/utils/utils.js
+42
-1
ecommerce/static/js/views/course_form_view.js
+30
-16
ecommerce/static/js/views/course_seat_form_fields/course_seat_form_field_view.js
+7
-38
ecommerce/static/js/views/course_seat_form_fields/credit_course_seat_form_field_row_view.js
+66
-0
ecommerce/static/js/views/course_seat_form_fields/credit_course_seat_form_field_view.js
+95
-0
ecommerce/static/js/views/course_seat_form_fields/professional_course_seat_form_field_view.js
+3
-19
ecommerce/static/templates/course_form.html
+1
-1
ecommerce/static/templates/credit_course_seat_form_field.html
+36
-0
ecommerce/static/templates/credit_course_seat_form_field_row.html
+42
-0
No files found.
ecommerce/static/js/models/course_model.js
View file @
d7b1f730
...
...
@@ -120,7 +120,7 @@ define([
*
* Note that audit seats cannot be created, only edited.
*/
creatableSeatTypes
:
[
'honor'
,
'verified'
,
'professional'
],
creatableSeatTypes
:
[
'honor'
,
'verified'
,
'professional'
,
'credit'
],
initialize
:
function
()
{
this
.
get
(
'products'
).
on
(
'change:id_verification_required'
,
this
.
triggerIdVerified
,
this
);
...
...
@@ -179,28 +179,30 @@ define([
},
/**
* Returns
an existing CourseSeat corresponding to the given seat type; or creates a new one,
*
if one is not found
.
* Returns
existing CourseSeats corresponding to the given seat type. If none
*
are not found, creates a new one
.
*
* @param {String} seatType
* @returns {CourseSeat}
* @returns {CourseSeat
[]
}
*/
getOrCreateSeat
:
function
(
seatType
)
{
getOrCreateSeat
s
:
function
(
seatType
)
{
var
seatClass
,
seat
=
_
.
find
(
this
.
seats
(),
function
(
product
)
{
// Find the seat with the specific seat type
seat
s
=
_
.
filter
(
this
.
seats
(),
function
(
product
)
{
// Find the seat
s
with the specific seat type
return
product
.
getSeatType
()
===
seatType
;
});
}),
seat
;
if
(
!
seat
&&
_
.
contains
(
this
.
creatableSeatTypes
,
seatType
))
{
if
(
_
.
isEmpty
(
seats
)
&&
_
.
contains
(
this
.
creatableSeatTypes
,
seatType
))
{
seatClass
=
CourseUtils
.
getCourseSeatModel
(
seatType
);
/*jshint newcap: false */
seat
=
new
seatClass
();
/*jshint newcap: true */
this
.
get
(
'products'
).
add
(
seat
);
seats
.
push
(
seat
);
}
return
seat
;
return
seat
s
;
},
/**
...
...
ecommerce/static/js/models/course_seats/course_seat.js
View file @
d7b1f730
...
...
@@ -10,12 +10,15 @@ define([
expires
:
null
,
id_verification_required
:
null
,
price
:
0
,
credit_provider
:
null
,
credit_hours
:
null
,
product_class
:
'Seat'
},
validation
:
{
price
:
{
required
:
true
,
pattern
:
'number'
,
msg
:
gettext
(
'All course seats must have a price.'
)
},
product_class
:
{
...
...
ecommerce/static/js/models/course_seats/credit_seat.js
0 → 100644
View file @
d7b1f730
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'
});
}
);
ecommerce/static/js/models/product_model.js
View file @
d7b1f730
...
...
@@ -54,9 +54,9 @@ define([
name
:
attribute
,
value
:
this
.
get
(
attribute
)
});
delete
data
[
attribute
];
}
delete
data
[
attribute
];
},
this
);
// Restore the timezone component, and output the ISO 8601 format expected by the server.
...
...
ecommerce/static/js/test/specs/models/course_model_spec.js
View file @
d7b1f730
...
...
@@ -17,8 +17,8 @@ define([
var
model
,
honorSeat
=
{
id
:
9
,
url
:
'http://ecommerce.local:8002/api/v2/products/
9
/'
,
id
:
8
,
url
:
'http://ecommerce.local:8002/api/v2/products/
8
/'
,
structure
:
'child'
,
product_class
:
'Seat'
,
title
:
'Seat in edX Demonstration Course with honor certificate'
,
...
...
@@ -41,8 +41,8 @@ define([
is_available_to_buy
:
true
},
verifiedSeat
=
{
id
:
8
,
url
:
'http://ecommerce.local:8002/api/v2/products/
8
/'
,
id
:
9
,
url
:
'http://ecommerce.local:8002/api/v2/products/
9
/'
,
structure
:
'child'
,
product_class
:
'Seat'
,
title
:
'Seat in edX Demonstration Course with verified certificate (and ID verification)'
,
...
...
@@ -64,17 +64,83 @@ define([
],
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
=
{
id
:
'edX/DemoX/Demo_Course'
,
url
:
'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/'
,
name
:
'edX Demonstration Course'
,
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/'
,
last_edited
:
'2015-07-27T00:27:23Z'
,
products
:
[
honorSeat
,
verifiedSeat
,
creditSeat
,
alternateCreditSeat
,
{
id
:
7
,
url
:
'http://ecommerce.local:8002/api/v2/products/7/'
,
...
...
@@ -111,14 +177,14 @@ define([
// Sanity check to ensure the products were properly parsed
products
=
model
.
get
(
'products'
);
expect
(
products
.
length
).
toEqual
(
3
);
expect
(
products
.
length
).
toEqual
(
5
);
// Remove the parent products
model
.
removeParentProducts
();
// Only the children survived...
expect
(
products
.
length
).
toEqual
(
2
);
expect
(
products
.
where
({
structure
:
'child'
}).
length
).
toEqual
(
2
);
expect
(
products
.
length
).
toEqual
(
4
);
expect
(
products
.
where
({
structure
:
'child'
}).
length
).
toEqual
(
4
);
});
});
...
...
@@ -190,31 +256,37 @@ define([
});
});
describe
(
'getOrCreateSeat'
,
function
()
{
describe
(
'getOrCreateSeat
s
'
,
function
()
{
it
(
'should return existing seats'
,
function
()
{
var
mapping
=
{
'honor'
:
honorSeat
,
'verified'
:
verifiedSeat
};
'honor'
:
[
honorSeat
],
'verified'
:
[
verifiedSeat
],
'credit'
:
[
creditSeat
,
alternateCreditSeat
]
},
seats
;
_
.
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
()
{
expect
(
model
.
getOrCreateSeat
(
'audit'
)).
toBeUndefined
(
);
it
(
'should return
an empty array
if an audit seat does not already exist'
,
function
()
{
expect
(
model
.
getOrCreateSeat
s
(
'audit'
)).
toEqual
([]
);
});
it
(
'should create a new CourseSeat if one does not exist'
,
function
()
{
var
seat
;
// 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
seat
=
model
.
getOrCreateSeat
(
'professional'
)
;
expect
(
model
.
seats
().
length
).
toEqual
(
3
);
seat
=
model
.
getOrCreateSeat
s
(
'professional'
)[
0
]
;
expect
(
model
.
seats
().
length
).
toEqual
(
5
);
// The new seat's class/type should correspond to the passed in seat type
expect
(
seat
).
toEqual
(
jasmine
.
any
(
ProfessionalSeat
));
...
...
@@ -229,7 +301,7 @@ define([
describe
(
'verification deadline validation'
,
function
()
{
it
(
'succeeds if the verification deadline is after the course seats
\'
expiration dates'
,
function
()
{
var
seat
=
model
.
getOrCreateSeat
(
'verified'
)
;
var
seat
=
model
.
getOrCreateSeat
s
(
'verified'
)[
0
]
;
model
.
set
(
'verification_deadline'
,
'2016-01-01T00:00:00Z'
);
seat
.
set
(
'expires'
,
'2015-01-01T00:00:00Z'
);
...
...
@@ -238,7 +310,7 @@ define([
});
it
(
'fails if the verification deadline is before the course seats
\'
expiration dates'
,
function
()
{
var
seat
=
model
.
getOrCreateSeat
(
'verified'
)
,
var
seat
=
model
.
getOrCreateSeat
s
(
'verified'
)[
0
]
,
msg
=
'The verification deadline must occur AFTER the upgrade deadline.'
;
model
.
set
(
'verification_deadline'
,
'2014-01-01T00:00:00Z'
);
seat
.
set
(
'expires'
,
'2015-01-01T00:00:00Z'
);
...
...
ecommerce/static/js/test/specs/views/course_edit_view_spec.js
View file @
d7b1f730
This diff is collapsed.
Click to expand it.
ecommerce/static/js/test/specs/views/course_seat_form_fields/professional_course_seat_form_field_view_spec.js
deleted
100644 → 0
View file @
faae1e32
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'
);
});
});
});
}
);
ecommerce/static/js/test/specs/views/course_seat_form_fields/verified_seat_form_field_view_spec.js
deleted
100644 → 0
View file @
faae1e32
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
);
});
});
});
}
);
ecommerce/static/js/utils/course_utils.js
View file @
d7b1f730
...
...
@@ -4,14 +4,16 @@ define([
'models/course_seats/course_seat'
,
'models/course_seats/honor_seat'
,
'models/course_seats/professional_seat'
,
'models/course_seats/verified_seat'
'models/course_seats/verified_seat'
,
'models/course_seats/credit_seat'
],
function
(
_
,
AuditSeat
,
CourseSeat
,
HonorSeat
,
ProfessionalSeat
,
VerifiedSeat
)
{
VerifiedSeat
,
CreditSeat
)
{
'use strict'
;
return
{
...
...
@@ -27,7 +29,7 @@ define([
* @returns {CourseSeat[]}
*/
getSeatModelMap
:
_
.
memoize
(
function
()
{
return
_
.
indexBy
([
AuditSeat
,
HonorSeat
,
ProfessionalSeat
,
VerifiedSeat
],
'seatType'
);
return
_
.
indexBy
([
AuditSeat
,
HonorSeat
,
ProfessionalSeat
,
VerifiedSeat
,
CreditSeat
],
'seatType'
);
}),
/**
...
...
@@ -74,7 +76,7 @@ define([
return
'residual'
;
});
}
,
}
};
}
);
ecommerce/static/js/utils/utils.js
View file @
d7b1f730
define
([
'backbone'
,
'backbone.validation'
,
'moment'
,
'underscore'
],
function
(
moment
,
function
(
Backbone
,
BackboneValidation
,
moment
,
_
)
{
'use strict'
;
...
...
@@ -81,6 +85,43 @@ define([
return
_
.
every
(
models
,
function
(
model
)
{
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'
);
}
});
}
};
}
...
...
ecommerce/static/js/views/course_form_view.js
View file @
d7b1f730
...
...
@@ -9,12 +9,14 @@ define([
'moment'
,
'underscore'
,
'underscore.string'
,
'collections/product_collection'
,
'text!templates/course_form.html'
,
'text!templates/_course_type_radio_field.html'
,
'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/verified_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'
,
'utils/course_utils'
],
...
...
@@ -26,12 +28,14 @@ define([
moment
,
_
,
_s
,
ProductCollection
,
CourseFormTemplate
,
CourseTypeRadioTemplate
,
AuditCourseSeatFormFieldView
,
HonorCourseSeatFormFieldView
,
VerifiedCourseSeatFormFieldView
,
ProfessionalCourseSeatFormFieldView
,
CreditCourseSeatFormFieldView
,
AlertView
,
CourseUtils
)
{
'use strict'
;
...
...
@@ -85,7 +89,7 @@ define([
type
:
'credit'
,
displayName
:
gettext
(
'Credit'
),
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([
audit
:
AuditCourseSeatFormFieldView
,
honor
:
HonorCourseSeatFormFieldView
,
verified
:
VerifiedCourseSeatFormFieldView
,
professional
:
ProfessionalCourseSeatFormFieldView
professional
:
ProfessionalCourseSeatFormFieldView
,
credit
:
CreditCourseSeatFormFieldView
},
events
:
{
...
...
@@ -188,18 +193,11 @@ define([
break
;
}
// TODO Activate credit seat
var
index
=
activeCourseTypes
.
indexOf
(
'credit'
);
if
(
index
>
-
1
)
{
activeCourseTypes
.
splice
(
index
,
1
);
}
return
activeCourseTypes
;
},
setLockedCourseType
:
function
()
{
this
.
lockedCourseType
=
this
.
model
.
get
(
'type'
);
this
.
renderCourseTypes
();
},
render
:
function
()
{
...
...
@@ -213,6 +211,9 @@ define([
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
;
},
...
...
@@ -263,19 +264,25 @@ define([
if
(
activeSeats
.
length
<
1
)
{
activeSeats
=
[
'empty'
];
}
else
{
_
.
each
(
CourseUtils
.
orderSeatTypesForDisplay
(
activeSeats
),
function
(
seatType
)
{
var
model
,
var
seats
,
viewClass
,
view
=
this
.
courseSeatViews
[
seatType
];
if
(
!
view
)
{
model
=
this
.
model
.
getOrCreateSeat
(
seatType
);
seats
=
this
.
model
.
getOrCreateSeats
(
seatType
);
// seats = new ProductCollection(this.model.getOrCreateSeats(seatType));
viewClass
=
this
.
courseSeatViewMappings
[
seatType
];
if
(
viewClass
&&
model
)
{
if
(
viewClass
&&
seats
.
length
>
0
)
{
/*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 */
view
.
render
();
...
...
@@ -287,7 +294,7 @@ define([
}
// Retrieve these after any new renderings.
$courseSeats
=
$courseSeatsContainer
.
find
(
'.
course-seat
'
);
$courseSeats
=
$courseSeatsContainer
.
find
(
'.
row
'
);
// Hide all seats
$courseSeats
.
hide
();
...
...
@@ -307,10 +314,17 @@ define([
*/
renderAlert
:
function
(
level
,
message
)
{
var
view
=
new
AlertView
({
level
:
level
,
title
:
gettext
(
'Error!'
),
message
:
message
});
view
.
render
();
this
.
$
el
.
find
(
'.alerts'
)
.
append
(
view
.
el
);
this
.
$
alerts
.
append
(
view
.
el
);
this
.
alertViews
.
push
(
view
);
$
(
'body'
).
animate
({
scrollTop
:
this
.
$alerts
.
offset
().
top
},
500
);
this
.
$alerts
.
focus
();
return
this
;
},
...
...
ecommerce/static/js/views/course_seat_form_fields/course_seat_form_field_view.js
View file @
d7b1f730
...
...
@@ -4,14 +4,16 @@ define([
'backbone.validation'
,
'backbone.stickit'
,
'underscore'
,
'underscore.string'
'underscore.string'
,
'utils/utils'
],
function
(
$
,
Backbone
,
BackboneValidation
,
BackboneStickit
,
_
,
_s
)
{
_s
,
Utils
)
{
'use strict'
;
return
Backbone
.
View
.
extend
({
...
...
@@ -35,55 +37,22 @@ define([
},
className
:
function
()
{
return
'row
course-seat '
+
this
.
seatType
;
return
'row
'
+
this
.
seatType
+
' course-seat'
;
},
initialize
:
function
()
{
/* istanbul ignore next */
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'
);
}
});
Utils
.
bindValidation
(
this
);
},
render
:
function
()
{
this
.
$el
.
html
(
this
.
template
(
this
.
model
.
attributes
));
this
.
stickit
();
return
this
;
},
cleanIdVerificationRequired
:
function
(
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
;
}
});
}
...
...
ecommerce/static/js/views/course_seat_form_fields/credit_course_seat_form_field_row_view.js
0 → 100644
View file @
d7b1f730
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
();
}
});
}
);
ecommerce/static/js/views/course_seat_form_fields/credit_course_seat_form_field_view.js
0 → 100644
View file @
d7b1f730
// 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
);
}
});
}
);
ecommerce/static/js/views/course_seat_form_fields/professional_course_seat_form_field_view.js
View file @
d7b1f730
define
([
'underscore.string'
,
'views/course_seat_form_fields/verified_course_seat_form_field_view'
,
'text!templates/professional_course_seat_form_field.html'
,
'backbone.super'
'text!templates/professional_course_seat_form_field.html'
],
function
(
_s
,
VerifiedCourseSeatFormFieldView
,
function
(
VerifiedCourseSeatFormFieldView
,
FieldTemplate
)
{
'use strict'
;
...
...
@@ -13,20 +10,7 @@ define([
certificateType
:
'professional'
,
idVerificationRequired
:
false
,
seatType
:
'professional'
,
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
;
}
template
:
_
.
template
(
FieldTemplate
)
});
}
);
ecommerce/static/templates/course_form.html
View file @
d7b1f730
<div
class=
"alerts"
></div>
<div
class=
"alerts"
tabindex=
"-1"
aria-live=
"polite"
></div>
<div
class=
"fields"
>
<div
class=
"form-group"
>
...
...
ecommerce/static/templates/credit_course_seat_form_field.html
0 → 100644
View file @
d7b1f730
<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>
ecommerce/static/templates/credit_course_seat_form_field_row.html
0 → 100644
View file @
d7b1f730
<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>
<
%
}
%
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment