2015-09-10 13:37:32 -04:00
|
|
|
app = require 'core/application'
|
2015-10-09 12:32:46 -04:00
|
|
|
AuthModal = require 'views/core/AuthModal'
|
2015-08-29 10:15:35 -04:00
|
|
|
CocoCollection = require 'collections/CocoCollection'
|
|
|
|
Course = require 'models/Course'
|
2015-09-10 13:37:32 -04:00
|
|
|
CourseInstance = require 'models/CourseInstance'
|
|
|
|
RootView = require 'views/core/RootView'
|
|
|
|
template = require 'templates/courses/courses'
|
|
|
|
utils = require 'core/utils'
|
2015-08-29 10:15:35 -04:00
|
|
|
|
2015-10-12 19:25:23 -04:00
|
|
|
# TODO: Hour of Code (HoC) integration is a mess
|
|
|
|
|
2015-08-29 10:15:35 -04:00
|
|
|
module.exports = class CoursesView extends RootView
|
|
|
|
id: 'courses-view'
|
|
|
|
template: template
|
|
|
|
|
2015-09-10 13:37:32 -04:00
|
|
|
events:
|
|
|
|
'click .btn-buy': 'onClickBuy'
|
|
|
|
'click .btn-enroll': 'onClickEnroll'
|
|
|
|
'click .btn-enter': 'onClickEnter'
|
2015-10-13 11:11:55 -04:00
|
|
|
'click .btn-hoc-student-continue': 'onClickHOCStudentContinue'
|
2015-09-10 13:37:32 -04:00
|
|
|
'click .btn-student': 'onClickStudent'
|
|
|
|
'click .btn-teacher': 'onClickTeacher'
|
|
|
|
|
2015-08-29 10:15:35 -04:00
|
|
|
constructor: (options) ->
|
2015-09-10 13:37:32 -04:00
|
|
|
super(options)
|
2015-10-13 11:11:55 -04:00
|
|
|
@setUpHourOfCode()
|
2015-09-10 13:37:32 -04:00
|
|
|
@praise = utils.getCoursePraise()
|
2015-10-07 17:37:41 -04:00
|
|
|
@studentMode = Backbone.history.getFragment()?.indexOf('courses/students') >= 0
|
2015-08-29 10:15:35 -04:00
|
|
|
@courses = new CocoCollection([], { url: "/db/course", model: Course})
|
|
|
|
@supermodel.loadCollection(@courses, 'courses')
|
2015-09-10 13:37:32 -04:00
|
|
|
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
|
|
|
|
@listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
|
|
|
|
@supermodel.loadCollection(@courseInstances, 'course_instances')
|
2015-10-07 17:56:10 -04:00
|
|
|
if prepaidCode = utils.getQueryVariable('_ppc', false)
|
|
|
|
if me.isAnonymous()
|
|
|
|
@state = 'ppc_logged_out'
|
|
|
|
else
|
2015-10-08 08:47:08 -04:00
|
|
|
@studentMode = true
|
2015-10-30 14:05:08 -04:00
|
|
|
@courseEnrollByURL(prepaidCode)
|
2015-08-29 10:15:35 -04:00
|
|
|
|
2015-10-13 11:11:55 -04:00
|
|
|
setUpHourOfCode: ->
|
|
|
|
# If we are coming in at /hoc, then we show the landing page.
|
|
|
|
# If we have ?hoc=true (for the step after the landing page), then we show any HoC-specific instructions.
|
|
|
|
# If we haven't tracked this player as an hourOfCode player yet, and it's a new account, we do that now.
|
|
|
|
@hocLandingPage = Backbone.history.getFragment()?.indexOf('hoc') >= 0
|
|
|
|
@hocMode = utils.getQueryVariable('hoc', false)
|
|
|
|
elapsed = new Date() - new Date(me.get('dateCreated'))
|
|
|
|
if not me.get('hourOfCode') and (@hocLandingPage or @hocMode) and elapsed < 5 * 60 * 1000
|
|
|
|
me.set('hourOfCode', true)
|
|
|
|
me.patch()
|
|
|
|
$('body').append($('<img src="https://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">'))
|
|
|
|
application.tracker?.trackEvent 'Hour of Code Begin'
|
|
|
|
if me.get('hourOfCode') and elapsed < 24 * 60 * 60 * 1000
|
|
|
|
@hocMode = true # If they really just arrived, make sure we're still in hocMode even if they lost ?hoc=true.
|
|
|
|
|
2015-08-29 10:15:35 -04:00
|
|
|
getRenderData: ->
|
|
|
|
context = super()
|
|
|
|
context.courses = @courses.models ? []
|
2015-09-10 13:37:32 -04:00
|
|
|
context.enrolledCourses = @enrolledCourses ? {}
|
2015-10-12 19:25:23 -04:00
|
|
|
context.hocLandingPage = @hocLandingPage
|
|
|
|
context.hocMode = @hocMode
|
2015-09-10 13:37:32 -04:00
|
|
|
context.instances = @courseInstances.models ? []
|
|
|
|
context.praise = @praise
|
2015-10-06 15:30:14 -04:00
|
|
|
context.state = @state
|
|
|
|
context.stateMessage = @stateMessage
|
2015-09-10 13:37:32 -04:00
|
|
|
context.studentMode = @studentMode
|
2015-08-29 10:15:35 -04:00
|
|
|
context
|
2015-09-10 13:37:32 -04:00
|
|
|
|
|
|
|
afterRender: ->
|
|
|
|
super()
|
|
|
|
@setupCoursesFAQPopover()
|
|
|
|
|
|
|
|
onCourseInstancesLoaded: ->
|
|
|
|
@enrolledCourses = {}
|
|
|
|
@enrolledCourses[courseInstance.get('courseID')] = true for courseInstance in @courseInstances.models
|
|
|
|
|
|
|
|
setupCoursesFAQPopover: ->
|
2015-09-25 15:51:51 -04:00
|
|
|
popoverTitle = "<h3>" + $.i18n.t('courses.faq') + "<button type='button' class='close' onclick='$('.courses-faq').popover('hide');'>×</button></h3>"
|
|
|
|
popoverContent = "<p><strong>" + $.i18n.t('courses.question') + "</strong> " + $.i18n.t('courses.question1') + "</p>"
|
|
|
|
popoverContent += "<p><strong>" + $.i18n.t('courses.answer') + "</strong> " + $.i18n.t('courses.answer1') + "</p>"
|
|
|
|
popoverContent += "<p>" + $.i18n.t('courses.answer2') + "</p>"
|
2015-09-10 13:37:32 -04:00
|
|
|
@$el.find('.courses-faq').popover(
|
|
|
|
animation: true
|
|
|
|
html: true
|
|
|
|
placement: 'top'
|
|
|
|
trigger: 'click'
|
|
|
|
title: popoverTitle
|
|
|
|
content: popoverContent
|
|
|
|
container: @$el
|
|
|
|
).on 'shown.bs.popover', =>
|
|
|
|
application.tracker?.trackEvent 'Subscription payment methods hover'
|
|
|
|
|
|
|
|
onClickBuy: (e) ->
|
2015-10-01 18:23:20 -04:00
|
|
|
$('.continue-dialog').modal('hide')
|
2015-09-10 13:37:32 -04:00
|
|
|
courseID = $(e.target).data('course-id')
|
|
|
|
route = "/courses/enroll/#{courseID}"
|
|
|
|
viewClass = require 'views/courses/CourseEnrollView'
|
|
|
|
viewArgs = [{}, courseID]
|
|
|
|
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
|
|
|
|
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
|
|
|
|
|
|
|
onClickEnroll: (e) ->
|
2015-10-09 12:32:46 -04:00
|
|
|
return @openModalView new AuthModal() if me.isAnonymous()
|
2015-10-06 14:20:53 -04:00
|
|
|
courseID = $(e.target).data('course-id')
|
2015-10-07 17:56:10 -04:00
|
|
|
prepaidCode = ($(".code-input[data-course-id=#{courseID}]").val() ? '').trim()
|
2015-10-30 14:05:08 -04:00
|
|
|
@courseEnrollByModal(prepaidCode)
|
2015-09-10 13:37:32 -04:00
|
|
|
|
|
|
|
onClickEnter: (e) ->
|
|
|
|
$('.continue-dialog').modal('hide')
|
|
|
|
courseID = $(e.target).data('course-id')
|
2015-09-24 20:52:00 -04:00
|
|
|
courseInstanceID = $(".select-session[data-course-id=#{courseID}]").val()
|
|
|
|
route = "/courses/#{courseID}/#{courseInstanceID}"
|
2015-09-10 13:37:32 -04:00
|
|
|
viewClass = require 'views/courses/CourseDetailsView'
|
2015-09-24 20:52:00 -04:00
|
|
|
viewArgs = [{}, courseID, courseInstanceID]
|
|
|
|
navigationEvent = route: route, viewClass: viewClass, viewArgs: viewArgs
|
2015-09-10 13:37:32 -04:00
|
|
|
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
|
|
|
|
2015-10-13 11:11:55 -04:00
|
|
|
onClickHOCStudentContinue: (e) ->
|
2015-10-12 19:25:23 -04:00
|
|
|
$('.continue-dialog').modal('hide')
|
2015-10-13 11:11:55 -04:00
|
|
|
if e
|
|
|
|
courseID = $(e.target).data('course-id')
|
|
|
|
else
|
|
|
|
courseID = '560f1a9f22961295f9427742'
|
2015-10-12 19:25:23 -04:00
|
|
|
|
|
|
|
@state = 'enrolling'
|
|
|
|
@stateMessage = undefined
|
|
|
|
@render?()
|
|
|
|
|
|
|
|
# TODO: Copied from CourseEnrollView
|
|
|
|
|
|
|
|
data =
|
|
|
|
name: 'Single Player'
|
|
|
|
seats: 9999
|
|
|
|
courseID: courseID
|
2015-10-13 11:11:55 -04:00
|
|
|
hourOfCode: true
|
2015-10-12 19:25:23 -04:00
|
|
|
jqxhr = $.post('/db/course_instance/-/create', data)
|
|
|
|
jqxhr.done (data, textStatus, jqXHR) =>
|
|
|
|
application.tracker?.trackEvent 'Finished HoC student course creation', {courseID: courseID}
|
|
|
|
# TODO: handle fetch errors
|
|
|
|
me.fetch(cache: false).always =>
|
|
|
|
courseID = courseID
|
|
|
|
route = "/courses/#{courseID}"
|
|
|
|
viewArgs = [{}, courseID]
|
|
|
|
if data?.length > 0
|
|
|
|
courseInstanceID = data[0]._id
|
|
|
|
route += "/#{courseInstanceID}"
|
|
|
|
viewArgs[0].courseInstanceID = courseInstanceID
|
|
|
|
Backbone.Mediator.publish 'router:navigate',
|
|
|
|
route: route
|
|
|
|
viewClass: 'views/courses/CourseDetailsView'
|
|
|
|
viewArgs: viewArgs
|
|
|
|
jqxhr.fail (xhr, textStatus, errorThrown) =>
|
|
|
|
console.error 'Got an error purchasing a course:', textStatus, errorThrown
|
|
|
|
application.tracker?.trackEvent 'Failed HoC student course creation', status: textStatus
|
|
|
|
if xhr.status is 402
|
|
|
|
@state = 'declined'
|
|
|
|
@stateMessage = arguments[2]
|
|
|
|
else
|
|
|
|
@state = 'unknown_error'
|
|
|
|
@stateMessage = "#{xhr.status}: #{xhr.responseText}"
|
|
|
|
@render?()
|
|
|
|
|
2015-09-10 13:37:32 -04:00
|
|
|
onClickStudent: (e) ->
|
2015-10-13 11:11:55 -04:00
|
|
|
if @supermodel.finished() and @hocLandingPage
|
|
|
|
# Automatically enroll in first course
|
|
|
|
@onClickHOCStudentContinue()
|
|
|
|
return
|
2015-10-07 17:37:41 -04:00
|
|
|
route = "/courses/students"
|
2015-10-12 19:25:23 -04:00
|
|
|
route += "?hoc=true" if @hocLandingPage or @hocMode
|
2015-09-10 13:37:32 -04:00
|
|
|
viewClass = require 'views/courses/CoursesView'
|
2015-10-07 17:37:41 -04:00
|
|
|
navigationEvent = route: route, viewClass: viewClass, viewArgs: []
|
2015-09-10 13:37:32 -04:00
|
|
|
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
|
|
|
|
|
|
|
onClickTeacher: (e) ->
|
2015-10-07 17:37:41 -04:00
|
|
|
route = "/courses/teachers"
|
2015-10-12 19:25:23 -04:00
|
|
|
route += "?hoc=true" if @hocLandingPage or @hocMode
|
2015-09-10 13:37:32 -04:00
|
|
|
viewClass = require 'views/courses/CoursesView'
|
2015-10-07 17:37:41 -04:00
|
|
|
navigationEvent = route: route, viewClass: viewClass, viewArgs: []
|
2015-09-10 13:37:32 -04:00
|
|
|
Backbone.Mediator.publish 'router:navigate', navigationEvent
|
2015-10-06 15:30:14 -04:00
|
|
|
|
2015-10-30 14:05:08 -04:00
|
|
|
courseEnrollByURL: (prepaidCode) ->
|
2015-10-06 15:30:14 -04:00
|
|
|
@state = 'enrolling'
|
|
|
|
@render?()
|
2015-10-30 14:05:08 -04:00
|
|
|
$.ajax({
|
|
|
|
method: 'POST'
|
|
|
|
url: '/db/course_instance/-/redeem_prepaid'
|
|
|
|
data: prepaidCode: prepaidCode
|
|
|
|
context: @
|
|
|
|
success: @onRedeemPrepaidSuccess
|
|
|
|
error: (xhr, textStatus, errorThrown) ->
|
|
|
|
console.error 'Got an error redeeming a course prepaid code:', textStatus, errorThrown
|
|
|
|
application.tracker?.trackEvent 'Failed to redeem course prepaid code by url', status: textStatus
|
|
|
|
@state = 'unknown_error'
|
|
|
|
@stateMessage = "Failed to redeem code: #{xhr.responseText}"
|
|
|
|
@render?()
|
|
|
|
})
|
|
|
|
|
|
|
|
courseEnrollByModal: (prepaidCode) ->
|
|
|
|
@state = 'enrolling-by-modal'
|
|
|
|
@renderSelectors '.student-dialog-state-row'
|
|
|
|
$.ajax({
|
|
|
|
method: 'POST'
|
|
|
|
url: '/db/course_instance/-/redeem_prepaid'
|
|
|
|
data: prepaidCode: prepaidCode
|
|
|
|
context: @
|
|
|
|
success: ->
|
|
|
|
$('.continue-dialog').modal('hide')
|
|
|
|
@onRedeemPrepaidSuccess(arguments...)
|
|
|
|
error: (jqxhr, textStatus, errorThrown) ->
|
|
|
|
application.tracker?.trackEvent 'Failed to redeem course prepaid code by modal', status: textStatus
|
|
|
|
@state = 'unknown_error'
|
|
|
|
if jqxhr.status is 422
|
|
|
|
@stateMessage = 'Please enter a code.'
|
|
|
|
else if jqxhr.status is 404
|
|
|
|
@stateMessage = 'Code not found.'
|
2015-10-06 15:30:14 -04:00
|
|
|
else
|
2015-10-30 14:05:08 -04:00
|
|
|
@stateMessage = "#{jqxhr.responseText}"
|
|
|
|
@renderSelectors '.student-dialog-state-row'
|
|
|
|
})
|
|
|
|
|
|
|
|
onRedeemPrepaidSuccess: (data, textStatus, jqxhr) ->
|
|
|
|
prepaidID = data[0]?.prepaidID
|
|
|
|
application.tracker?.trackEvent 'Redeemed course prepaid code', {prepaidCode: prepaidID}
|
|
|
|
me.fetch(cache: false).always =>
|
|
|
|
if data?.length > 0 && data[0].courseID && data[0]._id
|
|
|
|
courseID = data[0].courseID
|
|
|
|
courseInstanceID = data[0]._id
|
|
|
|
route = "/courses/#{courseID}/#{courseInstanceID}"
|
|
|
|
viewArgs = [{}, courseID, courseInstanceID]
|
|
|
|
Backbone.Mediator.publish 'router:navigate',
|
|
|
|
route: route
|
|
|
|
viewClass: 'views/courses/CourseDetailsView'
|
|
|
|
viewArgs: viewArgs
|
|
|
|
else
|
|
|
|
@state = 'unknown_error'
|
|
|
|
@stateMessage = "Database error."
|
|
|
|
@render?()
|
|
|
|
|