mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-23 19:32:03 -04:00
Add functionality to TeacherCoursesView
This commit is contained in:
parent
7d686c5194
commit
4c4b301925
3 changed files with 223 additions and 6 deletions
|
@ -1 +1,18 @@
|
||||||
//#teacher-courses-view
|
#teacher-courses-view
|
||||||
|
margin-bottom: 50px
|
||||||
|
|
||||||
|
#fixed-area
|
||||||
|
position: fixed
|
||||||
|
bottom: 0
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
|
||||||
|
.well
|
||||||
|
margin-bottom: 0
|
||||||
|
padding: 5px
|
||||||
|
|
||||||
|
.col-sm-5
|
||||||
|
padding-top: 8px
|
||||||
|
|
||||||
|
.progress
|
||||||
|
margin-bottom: 0
|
|
@ -6,9 +6,20 @@ block content
|
||||||
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
|
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
|
||||||
|
|
||||||
hr
|
hr
|
||||||
|
|
||||||
|
p Create a class and add students to it.
|
||||||
|
|
||||||
|
- var totalRedeemers = view.prepaids.totalRedeemers();
|
||||||
|
- var totalMaxRedeemers = view.prepaids.totalMaxRedeemers();
|
||||||
|
|
||||||
|
.text-right
|
||||||
|
span.spr Available paid seats: #{totalRedeemers}/#{totalMaxRedeemers}
|
||||||
|
a.btn.btn-default.btn-xs(href="/courses/purchase") Add
|
||||||
|
|
||||||
for classroom in view.classrooms.models
|
for classroom in view.classrooms.models
|
||||||
h2= classroom.get('name')
|
h2= classroom.get('name')
|
||||||
|
|
||||||
|
- var courseInstances = view.courseInstances.where({classroomID: classroom.id})
|
||||||
|
|
||||||
if classroom.saving
|
if classroom.saving
|
||||||
.progress
|
.progress
|
||||||
|
@ -17,13 +28,32 @@ block content
|
||||||
table.table
|
table.table
|
||||||
tr
|
tr
|
||||||
th Student
|
th Student
|
||||||
for course in view.courses.models
|
for courseInstance in courseInstances
|
||||||
th= course.get('name')
|
th
|
||||||
|
if courseInstance.course
|
||||||
|
| #{courseInstance.course.get('name')}
|
||||||
|
|
||||||
if !_.size(classroom.get('members'))
|
if !_.size(classroom.get('members'))
|
||||||
tr
|
tr
|
||||||
td(colspan=1+view.courses.size())
|
td(colspan=1+view.courses.size())
|
||||||
em No students in this class yet.
|
em No students in this class yet.
|
||||||
|
|
||||||
|
for member in classroom.get('members') || []
|
||||||
|
- var user = view.members.get(member);
|
||||||
|
if !user
|
||||||
|
- continue;
|
||||||
|
tr
|
||||||
|
td= user.get('name')
|
||||||
|
for courseInstance in courseInstances
|
||||||
|
td
|
||||||
|
if _.contains(courseInstance.get('members'), user.id)
|
||||||
|
span.glyphicon.glyphicon-ok
|
||||||
|
else
|
||||||
|
input.course-instance-membership-checkbox(
|
||||||
|
type='checkbox'
|
||||||
|
data-course-instance-id=courseInstance.id
|
||||||
|
data-user-id=user.id
|
||||||
|
)
|
||||||
|
|
||||||
button.add-students-btn.btn.btn-sm(data-classroom-id=classroom.id) Add Students
|
button.add-students-btn.btn.btn-sm(data-classroom-id=classroom.id) Add Students
|
||||||
|
|
||||||
|
@ -33,4 +63,30 @@ block content
|
||||||
.col-sm-3.col-sm-offset-3
|
.col-sm-3.col-sm-offset-3
|
||||||
button#create-new-class-btn.btn.btn-default.btn-block Create New Class
|
button#create-new-class-btn.btn.btn-default.btn-block Create New Class
|
||||||
.col-sm-3
|
.col-sm-3
|
||||||
input#new-classroom-name-input.form-control(placeholder='new class name')
|
input#new-classroom-name-input.form-control(placeholder='new class name')
|
||||||
|
|
||||||
|
#fixed-area
|
||||||
|
.container
|
||||||
|
.row.well
|
||||||
|
if view.state === 'saving-changes'
|
||||||
|
p Saving changes
|
||||||
|
- var total = view.membershipAdditions.originalSize + view.usersToRedeem.originalSize;
|
||||||
|
- var left = view.membershipAdditions.size() + view.usersToRedeem.size();
|
||||||
|
- var pct = Math.max(10, (100 * (total - left) / total)).toFixed(1) + '%';
|
||||||
|
.progress.progress-striped.active
|
||||||
|
.progress-bar(style="width: #{pct}")
|
||||||
|
else
|
||||||
|
- var seatsLeft = totalMaxRedeemers - totalRedeemers - view.usersToRedeem.size();
|
||||||
|
if seatsLeft < 0
|
||||||
|
.alert.alert-danger
|
||||||
|
span.spr You do not have enough seats to accommodate all students you have selected.
|
||||||
|
a(href="/courses/purchase") Buy more seats.
|
||||||
|
else
|
||||||
|
.col-sm-2
|
||||||
|
button#save-changes-btn.btn.btn-primary.btn-block(disabled=!view.numCourseInstancesToAddTo) Save Changes
|
||||||
|
.col-sm-5
|
||||||
|
| Students to add to courses: #{view.numCourseInstancesToAddTo || 0}
|
||||||
|
.col-sm-5
|
||||||
|
| Seats to expend: #{view.usersToRedeem.size()} (will have #{seatsLeft} seats left)
|
||||||
|
|
||||||
|
block footer
|
|
@ -1,8 +1,11 @@
|
||||||
app = require 'core/application'
|
app = require 'core/application'
|
||||||
AuthModal = require 'views/core/AuthModal'
|
AuthModal = require 'views/core/AuthModal'
|
||||||
CocoCollection = require 'collections/CocoCollection'
|
CocoCollection = require 'collections/CocoCollection'
|
||||||
|
CocoModel = require 'models/CocoModel'
|
||||||
Course = require 'models/Course'
|
Course = require 'models/Course'
|
||||||
Classroom = require 'models/Classroom'
|
Classroom = require 'models/Classroom'
|
||||||
|
User = require 'models/User'
|
||||||
|
Prepaid = require 'models/Prepaid'
|
||||||
CourseInstance = require 'models/CourseInstance'
|
CourseInstance = require 'models/CourseInstance'
|
||||||
RootView = require 'views/core/RootView'
|
RootView = require 'views/core/RootView'
|
||||||
template = require 'templates/courses/teacher-courses-view'
|
template = require 'templates/courses/teacher-courses-view'
|
||||||
|
@ -16,16 +19,38 @@ module.exports = class TeacherCoursesView extends RootView
|
||||||
events:
|
events:
|
||||||
'click #create-new-class-btn': 'onClickCreateNewclassButton'
|
'click #create-new-class-btn': 'onClickCreateNewclassButton'
|
||||||
'click .add-students-btn': 'onClickAddStudentsButton'
|
'click .add-students-btn': 'onClickAddStudentsButton'
|
||||||
|
'click .course-instance-membership-checkbox': 'onClickCourseInstanceMembershipCheckbox'
|
||||||
|
'click #save-changes-btn': 'onClickSaveChangesButton'
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super(options)
|
super(options)
|
||||||
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
||||||
@supermodel.loadCollection(@courses, 'courses')
|
@supermodel.loadCollection(@courses, 'courses')
|
||||||
@classrooms = new CocoCollection([], { url: "/db/classroom", model: Classroom })
|
@classrooms = new CocoCollection([], { url: "/db/classroom", model: Classroom })
|
||||||
@listenToOnce @classrooms, 'sync', @onCourseInstancesLoaded
|
@classrooms.comparator = '_id'
|
||||||
|
@listenToOnce @classrooms, 'sync', @onceClassroomsSync
|
||||||
@supermodel.loadCollection(@classrooms, 'classrooms', {data: {ownerID: me.id}})
|
@supermodel.loadCollection(@classrooms, 'classrooms', {data: {ownerID: me.id}})
|
||||||
|
@courseInstances = new CocoCollection([], { url: "/db/course_instance", model: CourseInstance })
|
||||||
|
@courseInstances.comparator = 'courseID'
|
||||||
|
@supermodel.loadCollection(@courseInstances, 'course_instances', {data: {ownerID: me.id}})
|
||||||
|
@members = new CocoCollection([], { model: User })
|
||||||
|
@prepaids = new CocoCollection([], { url: "/db/prepaid", model: Prepaid })
|
||||||
|
sum = (numbers) -> _.reduce(numbers, (a, b) -> a + b)
|
||||||
|
@prepaids.totalMaxRedeemers = -> sum((prepaid.get('maxRedeemers') for prepaid in @models))
|
||||||
|
@prepaids.totalRedeemers = -> sum((_.size(prepaid.get('redeemers')) for prepaid in @models))
|
||||||
|
@prepaids.comparator = '_id'
|
||||||
|
@supermodel.loadCollection(@prepaids, 'prepaids', {data: {creator: me.id}})
|
||||||
|
@listenTo @members, 'sync', @render
|
||||||
|
@usersToRedeem = new CocoCollection([], { model: User })
|
||||||
@
|
@
|
||||||
|
|
||||||
|
onceClassroomsSync: ->
|
||||||
|
for classroom in @classrooms.models
|
||||||
|
@members.fetch({
|
||||||
|
remove: false
|
||||||
|
url: "/db/classroom/#{classroom.id}/members"
|
||||||
|
})
|
||||||
|
|
||||||
onClickCreateNewclassButton: ->
|
onClickCreateNewclassButton: ->
|
||||||
name = @$('#new-classroom-name-input').val()
|
name = @$('#new-classroom-name-input').val()
|
||||||
return unless name
|
return unless name
|
||||||
|
@ -42,4 +67,123 @@ module.exports = class TeacherCoursesView extends RootView
|
||||||
classroomID = $(e.target).data('classroom-id')
|
classroomID = $(e.target).data('classroom-id')
|
||||||
classroom = @classrooms.get(classroomID)
|
classroom = @classrooms.get(classroomID)
|
||||||
modal = new InviteToClassroomModal({classroom: classroom})
|
modal = new InviteToClassroomModal({classroom: classroom})
|
||||||
@openModalView(modal)
|
@openModalView(modal)
|
||||||
|
|
||||||
|
onLoaded: ->
|
||||||
|
super()
|
||||||
|
@linkCourseIntancesToCourses()
|
||||||
|
@fillMissingCourseInstances()
|
||||||
|
|
||||||
|
linkCourseIntancesToCourses: ->
|
||||||
|
for courseInstance in @courseInstances.models
|
||||||
|
courseInstance.course = @courses.get(courseInstance.get('courseID'))
|
||||||
|
|
||||||
|
fillMissingCourseInstances: ->
|
||||||
|
# TODO: Give teachers control over which courses are enabled for a given class.
|
||||||
|
# Add/remove course instances and columns in the view to match.
|
||||||
|
for classroom in @classrooms.models
|
||||||
|
for course in @courses.models
|
||||||
|
courseInstance = @courseInstances.findWhere({classroomID: classroom.id, courseID: course.id})
|
||||||
|
if not courseInstance
|
||||||
|
courseInstance = new CourseInstance({
|
||||||
|
classroomID: classroom.id
|
||||||
|
courseID: course.id
|
||||||
|
})
|
||||||
|
# TODO: figure out a better way to get around triggering validation errors for properties
|
||||||
|
# that the server will end up filling in, like an empty members array, ownerID
|
||||||
|
courseInstance.save(null, {validate: false})
|
||||||
|
courseInstance.course = course
|
||||||
|
@courseInstances.add(courseInstance)
|
||||||
|
@listenToOnce courseInstance, 'sync', @fillMissingCourseInstances
|
||||||
|
return
|
||||||
|
|
||||||
|
onClickCourseInstanceMembershipCheckbox: ->
|
||||||
|
usersToRedeem = {}
|
||||||
|
checkedBoxes = @$('.course-instance-membership-checkbox:checked')
|
||||||
|
_.each checkedBoxes, (el) =>
|
||||||
|
$el = $(el)
|
||||||
|
userID = $el.data('user-id')
|
||||||
|
return if usersToRedeem[userID]
|
||||||
|
user = @members.get(userID)
|
||||||
|
return if user.get('coursePrepaidID')
|
||||||
|
courseInstanceID = $el.data('course-instance-id')
|
||||||
|
courseInstance = @courseInstances.get(courseInstanceID)
|
||||||
|
return if courseInstance.course.get('free')
|
||||||
|
usersToRedeem[userID] = user
|
||||||
|
|
||||||
|
@usersToRedeem = new CocoCollection(_.values(usersToRedeem), {model: User})
|
||||||
|
@numCourseInstancesToAddTo = checkedBoxes.length
|
||||||
|
@renderSelectors '#fixed-area'
|
||||||
|
|
||||||
|
onClickSaveChangesButton: ->
|
||||||
|
@$('.course-instance-membership-checkbox').attr('disabled', true)
|
||||||
|
checkedBoxes = @$('.course-instance-membership-checkbox:checked')
|
||||||
|
raw = _.map checkedBoxes, (el) =>
|
||||||
|
$el = $(el)
|
||||||
|
userID = $el.data('user-id')
|
||||||
|
courseInstanceID = $el.data('course-instance-id')
|
||||||
|
courseInstance = @courseInstances.get(courseInstanceID)
|
||||||
|
return {
|
||||||
|
courseInstance: courseInstance
|
||||||
|
userID: userID
|
||||||
|
}
|
||||||
|
@membershipAdditions = new CocoCollection(raw, { model: User }) # TODO: Allow collections not to have models defined?
|
||||||
|
@membershipAdditions.originalSize = @membershipAdditions.size()
|
||||||
|
@usersToRedeem.originalSize = @usersToRedeem.size()
|
||||||
|
@state = 'saving-changes'
|
||||||
|
@renderSelectors '#fixed-area'
|
||||||
|
@redeemUsers()
|
||||||
|
|
||||||
|
redeemUsers: ->
|
||||||
|
if not @usersToRedeem.size()
|
||||||
|
@addMemberships()
|
||||||
|
return
|
||||||
|
|
||||||
|
user = @usersToRedeem.first()
|
||||||
|
prepaid = @prepaids.find (prepaid) -> prepaid.openSpots()
|
||||||
|
$.ajax({
|
||||||
|
method: 'POST'
|
||||||
|
url: _.result(prepaid, 'url') + '/redeemers'
|
||||||
|
data: { userID: user.id }
|
||||||
|
context: @
|
||||||
|
success: ->
|
||||||
|
@usersToRedeem.remove(user)
|
||||||
|
@renderSelectors '#fixed-area'
|
||||||
|
@redeemUsers()
|
||||||
|
error: (jqxhr, textStatus, errorThrown) ->
|
||||||
|
if jqxhr.status is 402
|
||||||
|
@state = 'error'
|
||||||
|
@stateMessage = arguments[2]
|
||||||
|
else
|
||||||
|
@state = 'error'
|
||||||
|
@stateMessage = "#{jqxhr.status}: #{jqxhr.responseText}"
|
||||||
|
@renderSelectors '#fixed-area'
|
||||||
|
})
|
||||||
|
|
||||||
|
addMemberships: ->
|
||||||
|
if not @membershipAdditions.size()
|
||||||
|
@renderSelectors '#fixed-area'
|
||||||
|
document.location.reload()
|
||||||
|
return
|
||||||
|
|
||||||
|
membershipAddition = @membershipAdditions.first()
|
||||||
|
courseInstance = membershipAddition.get('courseInstance')
|
||||||
|
userID = membershipAddition.get('userID')
|
||||||
|
$.ajax({
|
||||||
|
method: 'POST'
|
||||||
|
url: _.result(courseInstance, 'url') + '/members'
|
||||||
|
data: { userID: userID }
|
||||||
|
context: @
|
||||||
|
success: ->
|
||||||
|
@membershipAdditions.remove(membershipAddition)
|
||||||
|
@renderSelectors '#fixed-area'
|
||||||
|
@addMemberships()
|
||||||
|
error: (jqxhr, textStatus, errorThrown) ->
|
||||||
|
if jqxhr.status is 402
|
||||||
|
@state = 'error'
|
||||||
|
@stateMessage = arguments[2]
|
||||||
|
else
|
||||||
|
@state = 'error'
|
||||||
|
@stateMessage = "#{jqxhr.status}: #{jqxhr.responseText}"
|
||||||
|
@renderSelectors '#fixed-area'
|
||||||
|
})
|
Loading…
Add table
Reference in a new issue