2015-09-13 01:01:59 -04:00
Campaign = require ' models/Campaign '
2015-09-03 14:04:40 -04:00
CocoCollection = require ' collections/CocoCollection '
Course = require ' models/Course '
CourseInstance = require ' models/CourseInstance '
2015-11-09 21:08:39 -05:00
Classroom = require ' models/Classroom '
2015-09-13 01:01:59 -04:00
LevelSession = require ' models/LevelSession '
RootView = require ' views/core/RootView '
template = require ' templates/courses/course-details '
User = require ' models/User '
utils = require ' core/utils '
2015-10-05 16:34:37 -04:00
Prepaid = require ' models/Prepaid '
2015-09-03 14:04:40 -04:00
2015-10-13 11:11:55 -04:00
autoplayedOnce = false
2015-09-03 14:04:40 -04:00
module.exports = class CourseDetailsView extends RootView
id: ' course-details-view '
template: template
2015-12-02 12:52:52 -05:00
teacherMode: false
singlePlayerMode: false
memberSort: ' nameAsc '
2015-09-03 14:04:40 -04:00
2015-09-13 01:01:59 -04:00
events:
' change .progress-expand-checkbox ' : ' onCheckExpandedProgress '
' click .btn-play-level ' : ' onClickPlayLevel '
2015-09-23 19:27:45 -04:00
' click .btn-select-instance ' : ' onClickSelectInstance '
2015-09-13 01:01:59 -04:00
' click .progress-member-header ' : ' onClickMemberHeader '
' click .progress-header ' : ' onClickProgressHeader '
2015-09-24 17:48:54 -04:00
' click .progress-level-cell ' : ' onClickProgressLevelCell '
2015-09-13 01:01:59 -04:00
' mouseenter .progress-level-cell ' : ' onMouseEnterPoint '
' mouseleave .progress-level-cell ' : ' onMouseLeavePoint '
2015-09-24 20:12:18 -04:00
constructor: (options, @courseID, @courseInstanceID) ->
2015-09-03 14:04:40 -04:00
super options
2015-09-24 20:52:00 -04:00
@ courseID ? = options . courseID
@ courseInstanceID ? = options . courseInstanceID
2015-11-09 21:08:39 -05:00
@classroom = new Classroom ( )
2015-09-23 19:27:45 -04:00
@course = @ supermodel . getModel ( Course , @ courseID ) or new Course _id: @ courseID
@ listenTo @ course , ' sync ' , @ onCourseSync
2015-10-05 16:34:37 -04:00
@prepaid = new Prepaid ( )
2015-09-23 19:27:45 -04:00
if @ course . loaded
@ onCourseSync ( )
else
@ supermodel . loadModel @ course , ' course '
2015-09-13 01:01:59 -04:00
getRenderData: ->
context = super ( )
context.campaign = @ campaign
context.conceptsCompleted = @ conceptsCompleted ? { }
context.course = @ course if @ course ? . loaded
context.courseInstance = @ courseInstance if @ courseInstance ? . loaded
2015-09-23 19:27:45 -04:00
context.courseInstances = @ courseInstances ? . models ? [ ]
2015-09-24 10:28:43 -04:00
context.instanceStats = @ instanceStats
2015-09-13 01:01:59 -04:00
context.levelConceptMap = @ levelConceptMap ? { }
context.memberSort = @ memberSort
2015-09-24 10:28:43 -04:00
context.memberStats = @ memberStats
2015-09-13 01:01:59 -04:00
context.memberUserMap = @ memberUserMap ? { }
2015-09-23 19:27:45 -04:00
context.noCourseInstance = @ noCourseInstance
context.noCourseInstanceSelected = @ noCourseInstanceSelected
2015-10-07 20:08:22 -04:00
context.pricePerSeat = @ course . get ( ' pricePerSeat ' )
2015-09-13 01:01:59 -04:00
context.showExpandedProgress = @ showExpandedProgress
context.sortedMembers = @ sortedMembers ? [ ]
context.userConceptStateMap = @ userConceptStateMap ? { }
context.userLevelStateMap = @ userLevelStateMap ? { }
2015-10-05 16:34:37 -04:00
context.document = document
2015-09-13 01:01:59 -04:00
context
2015-12-02 12:52:52 -05:00
afterRender: ->
super ( )
if @ supermodel . finished ( ) and @ courseComplete and me . isAnonymous ( ) and @ options . justBeatLevel
# TODO: Make an intermediate modal that tells them they've finished HoC and has some snazzy stuff for convincing players to sign up instead of just throwing up the bare AuthModal
AuthModal = require ' views/core/AuthModal '
@ openModalView new AuthModal showSignupRationale: true
2015-09-13 01:01:59 -04:00
onCourseSync: ->
2015-12-02 12:52:52 -05:00
return if @ destroyed
2015-09-13 01:01:59 -04:00
# console.log 'onCourseSync'
2015-11-11 12:07:16 -05:00
if me . isAnonymous ( ) and ( not me . get ( ' hourOfCode ' ) and not @ course . get ( ' hourOfCode ' ) )
2015-09-23 19:27:45 -04:00
@noCourseInstance = true
2015-12-02 12:52:52 -05:00
@ render ( )
2015-09-23 19:27:45 -04:00
return
2015-09-13 01:01:59 -04:00
return if @ campaign ?
2015-09-23 19:27:45 -04:00
campaignID = @ course . get ( ' campaignID ' )
@campaign = @ supermodel . getModel ( Campaign , campaignID ) or new Campaign _id: campaignID
2015-09-13 01:01:59 -04:00
@ listenTo @ campaign , ' sync ' , @ onCampaignSync
2015-09-23 19:27:45 -04:00
if @ campaign . loaded
@ onCampaignSync ( )
else
@ supermodel . loadModel @ campaign , ' campaign '
2015-12-02 12:52:52 -05:00
@ render ( )
2015-09-13 01:01:59 -04:00
onCampaignSync: ->
2015-12-02 12:52:52 -05:00
return if @ destroyed
2015-09-13 01:01:59 -04:00
# console.log 'onCampaignSync'
2015-09-03 14:04:40 -04:00
if @ courseInstanceID
2015-09-13 01:01:59 -04:00
@ loadCourseInstance ( @ courseInstanceID )
2015-11-12 12:57:34 -05:00
else unless me . isAnonymous ( )
2015-12-02 12:52:52 -05:00
@ loadCourseInstances ( )
2015-09-13 01:01:59 -04:00
@levelConceptMap = { }
for levelID , level of @ campaign . get ( ' levels ' )
@ levelConceptMap [ levelID ] ? = { }
for concept in level . concepts
@ levelConceptMap [ levelID ] [ concept ] = true
2015-12-02 12:52:52 -05:00
if level . type is ' course-ladder '
@arenaLevel = level
@ render ( )
loadCourseInstances: ->
@courseInstances = new CocoCollection [ ] , { url: " /db/user/ #{ me . id } /course_instances " , model: CourseInstance , comparator: ' courseID ' }
@ listenToOnce @ courseInstances , ' sync ' , @ onCourseInstancesSync
@ supermodel . loadCollection @ courseInstances , ' course_instances '
loadAllCourses: ->
@allCourses = new CocoCollection [ ] , { url: " /db/course " , model: Course , comparator: ' _id ' }
@ listenToOnce @ allCourses , ' sync ' , @ onAllCoursesSync
@ supermodel . loadCollection @ allCourses , ' courses '
2015-09-03 14:04:40 -04:00
2015-09-13 01:01:59 -04:00
loadCourseInstance: (courseInstanceID) ->
2015-12-02 12:52:52 -05:00
return if @ destroyed
2015-09-13 01:01:59 -04:00
# console.log 'loadCourseInstance'
return if @ courseInstance ?
2015-09-24 20:12:18 -04:00
@courseInstanceID = courseInstanceID
@courseInstance = @ supermodel . getModel ( CourseInstance , @ courseInstanceID ) or new CourseInstance _id: @ courseInstanceID
2015-09-13 01:01:59 -04:00
@ listenTo @ courseInstance , ' sync ' , @ onCourseInstanceSync
2015-09-23 19:27:45 -04:00
if @ courseInstance . loaded
@ onCourseInstanceSync ( )
else
@courseInstance = @ supermodel . loadModel ( @ courseInstance , ' course_instance ' ) . model
2015-09-03 14:04:40 -04:00
2015-09-13 01:01:59 -04:00
onCourseInstancesSync: ->
2015-12-02 12:52:52 -05:00
return if @ destroyed
2015-09-13 01:01:59 -04:00
# console.log 'onCourseInstancesSync'
2015-12-02 12:52:52 -05:00
@ findNextCourseInstance ( )
if not @ courseInstance
# We are loading these to find the one we want to display.
if @ courseInstances . models . length is 1
@ loadCourseInstance ( @ courseInstances . models [ 0 ] . id )
2015-09-23 19:27:45 -04:00
else
2015-12-02 12:52:52 -05:00
if @ courseInstances . models . length is 0
@noCourseInstance = true
else
@noCourseInstanceSelected = true
@ render ( )
2015-09-13 01:01:59 -04:00
onCourseInstanceSync: ->
2015-12-02 12:52:52 -05:00
return if @ destroyed
2015-09-23 19:27:45 -04:00
# console.log 'onCourseInstanceSync'
2015-11-09 21:08:39 -05:00
if @ courseInstance . get ( ' classroomID ' )
@classroom = new Classroom ( { _id: @ courseInstance . get ( ' classroomID ' ) } )
@ supermodel . loadModel @ classroom , ' classroom '
2015-12-02 12:52:52 -05:00
@singlePlayerMode = @ courseInstance . get ( ' name ' ) is ' Single Player '
@teacherMode = @ courseInstance . get ( ' ownerID ' ) is me . id and not @ singlePlayerMode
@levelSessions = new CocoCollection ( [ ] , { url: " /db/course_instance/ #{ @ courseInstance . id } /level_sessions " , model: LevelSession , comparator: ' _id ' } )
2015-09-13 01:01:59 -04:00
@ listenToOnce @ levelSessions , ' sync ' , @ onLevelSessionsSync
@ supermodel . loadCollection @ levelSessions , ' level_sessions ' , cache: false
@members = new CocoCollection ( [ ] , { url: " /db/course_instance/ #{ @ courseInstance . id } /members " , model: User , comparator: ' nameLower ' } )
@ listenToOnce @ members , ' sync ' , @ onMembersSync
@ supermodel . loadCollection @ members , ' members ' , cache: false
2015-10-27 20:47:48 -04:00
@owner = new User ( { _id: @ courseInstance . get ( ' ownerID ' ) } )
@ supermodel . loadModel @ owner , ' user '
2015-12-02 12:52:52 -05:00
if @ teacherMode and prepaidID = @ courseInstance . get ( ' prepaidID ' )
2015-10-05 16:34:37 -04:00
@prepaid = @ supermodel . getModel ( Prepaid , prepaidID ) or new Prepaid _id: prepaidID
@ listenTo @ prepaid , ' sync ' , @ onPrepaidSync
if @ prepaid . loaded
@ onPrepaidSync ( )
else
@ supermodel . loadModel @ prepaid , ' prepaid '
2015-12-02 12:52:52 -05:00
@ render ( )
2015-10-05 16:34:37 -04:00
onPrepaidSync: ->
2015-12-02 12:52:52 -05:00
return if @ destroyed
# TODO: why do we rerender here? Template doesn't use prepaid.
@ render ( )
2015-09-13 01:01:59 -04:00
onLevelSessionsSync: ->
2015-12-02 12:52:52 -05:00
return if @ destroyed
2015-09-13 01:01:59 -04:00
# console.log 'onLevelSessionsSync'
2015-09-24 10:28:43 -04:00
@instanceStats = averageLevelsCompleted: 0 , furthestLevelCompleted: ' ' , totalLevelsCompleted: 0 , totalPlayTime: 0
@memberStats = { }
2015-09-13 01:01:59 -04:00
@userConceptStateMap = { }
2015-09-24 17:48:54 -04:00
@userLevelSessionMap = { }
2015-09-13 01:01:59 -04:00
@userLevelStateMap = { }
2015-09-24 10:28:43 -04:00
levelStateMap = { }
2015-09-13 01:01:59 -04:00
for levelSession in @ levelSessions . models
2015-12-01 20:23:33 -05:00
continue if levelSession . skipMe # Don't track second arena session as another completed level
2015-09-13 01:01:59 -04:00
userID = levelSession . get ( ' creator ' )
levelID = levelSession . get ( ' level ' ) . original
state = if levelSession . get ( ' state ' ) ? . complete then ' complete ' else ' started '
2015-12-01 20:23:33 -05:00
playtime = parseInt ( levelSession . get ( ' playtime ' ) ? 0 , 10 )
do (userID, levelID) =>
secondSessionForLevel = _ . find ( @ levelSessions . models , ( (otherSession) ->
otherSession . get ( ' creator ' ) is userID and otherSession . get ( ' level ' ) . original is levelID and otherSession . id isnt levelSession . id
) )
if secondSessionForLevel
state = ' complete ' if secondSessionForLevel . get ( ' state ' ) ? . complete
playtime = playtime + parseInt ( secondSessionForLevel . get ( ' playtime ' ) ? 0 , 10 )
secondSessionForLevel.skipMe = true
2015-09-24 10:28:43 -04:00
levelStateMap [ levelID ] = state
@ instanceStats . totalLevelsCompleted ++ if state is ' complete '
2015-12-01 20:23:33 -05:00
@ instanceStats . totalPlayTime += playtime
2015-09-24 10:28:43 -04:00
@ memberStats [ userID ] ? = totalLevelsCompleted: 0 , totalPlayTime: 0
@ memberStats [ userID ] . totalLevelsCompleted ++ if state is ' complete '
2015-12-01 20:23:33 -05:00
@ memberStats [ userID ] . totalPlayTime += playtime
2015-09-24 10:28:43 -04:00
@ userConceptStateMap [ userID ] ? = { }
2015-09-13 01:01:59 -04:00
for concept of @ levelConceptMap [ levelID ]
@ userConceptStateMap [ userID ] [ concept ] = state
2015-09-24 10:28:43 -04:00
2015-09-24 17:48:54 -04:00
@ userLevelSessionMap [ userID ] ? = { }
@ userLevelSessionMap [ userID ] [ levelID ] = levelSession
2015-09-24 10:28:43 -04:00
@ userLevelStateMap [ userID ] ? = { }
@ userLevelStateMap [ userID ] [ levelID ] = state
if @ courseInstance . get ( ' members ' ) . length > 0
@instanceStats.averageLevelsCompleted = @ instanceStats . totalLevelsCompleted / @ courseInstance . get ( ' members ' ) . length
2015-10-07 20:14:56 -04:00
@instanceStats.averageLevelPlaytime = @ instanceStats . totalPlayTime / @ courseInstance . get ( ' members ' ) . length
2015-09-24 10:28:43 -04:00
for levelID , level of @ campaign . get ( ' levels ' )
@instanceStats.furthestLevelCompleted = level . name if levelStateMap [ levelID ] is ' complete '
2015-09-13 01:01:59 -04:00
@conceptsCompleted = { }
for userID , conceptStateMap of @ userConceptStateMap
for concept , state of conceptStateMap
@ conceptsCompleted [ concept ] ? = 0
@ conceptsCompleted [ concept ] ++
2015-12-01 20:23:33 -05:00
2015-12-02 12:52:52 -05:00
if @ memberStats [ me . id ] ? . totalLevelsCompleted >= _ . size ( @ campaign . get ( ' levels ' ) ) - 1 # Don't need to complete arena
2015-12-01 20:23:33 -05:00
@courseComplete = true
2015-12-02 12:52:52 -05:00
@ loadCourseInstances ( ) unless @ courseInstances # Find the next course instance to do.
2015-12-01 20:23:33 -05:00
2015-12-02 12:52:52 -05:00
@ render ( )
2015-09-13 01:01:59 -04:00
2015-10-13 11:11:55 -04:00
# If we just joined a single-player course for Hour of Code, we automatically play.
2015-12-02 12:52:52 -05:00
if @ instanceStats . totalLevelsCompleted is 0 and @ instanceStats . totalPlayTime is 0 and @ singlePlayerMode and not autoplayedOnce
2015-10-13 11:11:55 -04:00
autoplayedOnce = true
@ $el . find ( ' button.btn-play-level ' ) . click ( )
2015-09-13 01:01:59 -04:00
onMembersSync: ->
2015-12-02 12:52:52 -05:00
return if @ destroyed
2015-09-13 01:01:59 -04:00
# console.log 'onMembersSync'
@memberUserMap = { }
for user in @ members . models
@ memberUserMap [ user . id ] = user
@ sortMembers ( )
2015-12-02 12:52:52 -05:00
@ render ( )
onAllCoursesSync: ->
@ findNextCourseInstance ( )
findNextCourseInstance: ->
@nextCourseInstance = _ . find @ courseInstances . models , (ci) =>
# Sorted by courseID
ci . get ( ' classroomID ' ) is @ courseInstance . get ( ' classroomID ' ) and ci . id isnt @ courseInstance . id and ci . get ( ' courseID ' ) > @ course . id
if @ nextCourseInstance
nextCourseID = @ nextCourseInstance . get ( ' courseID ' )
@nextCourse = @ supermodel . getModel ( Course , nextCourseID ) or new Course _id: nextCourseID
@nextCourse = @ supermodel . loadModel ( @ nextCourse , ' course ' ) . model
else if @ allCourses ? . loaded
@nextCourse = _ . find @ allCourses . models , (course) => course . id > @ course . id
else
@ loadAllCourses ( )
2015-09-13 01:01:59 -04:00
onCheckExpandedProgress: (e) ->
@showExpandedProgress = $ ( ' .progress-expand-checkbox ' ) . prop ( ' checked ' )
# TODO: why does render reset the checkbox to be unchecked?
2015-12-02 12:52:52 -05:00
@ render ( )
2015-09-13 01:01:59 -04:00
$ ( ' .progress-expand-checkbox ' ) . attr ( ' checked ' , @ showExpandedProgress )
onClickMemberHeader: (e) ->
@memberSort = if @ memberSort is ' nameAsc ' then ' nameDesc ' else ' nameAsc '
@ sortMembers ( )
2015-12-02 12:52:52 -05:00
@ render ( )
2015-09-13 01:01:59 -04:00
onClickProgressHeader: (e) ->
@memberSort = if @ memberSort is ' progressAsc ' then ' progressDesc ' else ' progressAsc '
@ sortMembers ( )
2015-12-02 12:52:52 -05:00
@ render ( )
2015-09-13 01:01:59 -04:00
onClickPlayLevel: (e) ->
levelSlug = $ ( e . target ) . data ( ' level-slug ' )
2015-11-18 17:02:45 -05:00
levelID = $ ( e . target ) . data ( ' level-id ' )
level = @ campaign . get ( ' levels ' ) [ levelID ]
if level . type is ' course-ladder '
2015-12-01 15:23:01 -05:00
viewClass = ' views/ladder/LadderView '
viewArgs = [ { supermodel: @ supermodel } , levelSlug ]
2015-11-19 16:20:21 -05:00
route = ' /play/ladder/ ' + levelSlug
2015-12-02 13:10:50 -05:00
unless @ singlePlayerMode # No league for solo courses
2015-12-01 15:23:01 -05:00
route += ' /course/ ' + @ courseInstance . id
viewArgs = viewArgs . concat [ ' course ' , @ courseInstance . id ]
2015-11-18 17:02:45 -05:00
else
2015-12-01 15:23:01 -05:00
route = @ getLevelURL levelSlug
viewClass = ' views/play/level/PlayLevelView '
viewArgs = [ { courseID: @ courseID , courseInstanceID: @ courseInstanceID , supermodel: @ supermodel } , levelSlug ]
Backbone . Mediator . publish ' router:navigate ' , route: route , viewClass: viewClass , viewArgs: viewArgs
2015-09-13 01:01:59 -04:00
2015-11-12 12:57:34 -05:00
getLevelURL: (levelSlug) ->
" /play/level/ #{ levelSlug } ?course= #{ @ courseID } &course-instance= #{ @ courseInstanceID } "
2015-09-23 19:27:45 -04:00
onClickSelectInstance: (e) ->
courseInstanceID = $ ( ' .select-instance ' ) . val ( )
@noCourseInstanceSelected = false
@ loadCourseInstance ( courseInstanceID )
2015-09-24 17:48:54 -04:00
onClickProgressLevelCell: (e) ->
2015-12-02 12:52:52 -05:00
return unless @ teacherMode or me . isAdmin ( )
2015-09-24 17:48:54 -04:00
levelID = $ ( e . currentTarget ) . data ( ' level-id ' )
levelSlug = $ ( e . currentTarget ) . data ( ' level-slug ' )
userID = $ ( e . currentTarget ) . data ( ' user-id ' )
return unless levelID and levelSlug and userID
2015-11-12 12:57:34 -05:00
route = @ getLevelURL levelSlug
2015-09-24 17:48:54 -04:00
if @ userLevelSessionMap [ userID ] ? [ levelID ]
2015-11-12 12:57:34 -05:00
route += " &session= #{ @ userLevelSessionMap [ userID ] [ levelID ] . id } &observing=true "
2015-09-24 17:48:54 -04:00
Backbone . Mediator . publish ' router:navigate ' , {
route: route
viewClass: ' views/play/level/PlayLevelView '
2015-12-01 15:23:01 -05:00
viewArgs: [ { supermodel: @ supermodel } , levelSlug ]
2015-09-24 17:48:54 -04:00
}
2015-09-13 01:01:59 -04:00
onMouseEnterPoint: (e) ->
2015-09-24 17:48:54 -04:00
$ ( ' .progress-popup-container ' ) . hide ( )
container = $ ( e . target ) . find ( ' .progress-popup-container ' ) . show ( )
2015-09-13 01:01:59 -04:00
margin = 20
offset = $ ( e . target ) . offset ( )
scrollTop = $ ( ' # page-container ' ) . scrollTop ( )
height = container . outerHeight ( )
container . css ( ' left ' , offset . left + e . offsetX )
container . css ( ' top ' , offset . top + scrollTop - height - margin )
onMouseLeavePoint: (e) ->
2015-09-24 17:48:54 -04:00
$ ( e . target ) . find ( ' .progress-popup-container ' ) . hide ( )
2015-09-13 01:01:59 -04:00
sortMembers: ->
# Progress sort precedence: most completed concepts, most started concepts, most levels, name sort
return unless @ campaign and @ courseInstance and @ memberUserMap
@sortedMembers = @ courseInstance . get ( ' members ' )
switch @ memberSort
when " nameDesc "
2015-10-09 12:27:30 -04:00
@ sortedMembers . sort (a, b) =>
aName = @ memberUserMap [ a ] ? . get ( ' name ' ) ? ' Anoner '
bName = @ memberUserMap [ b ] ? . get ( ' name ' ) ? ' Anoner '
bName . localeCompare ( aName )
2015-09-13 01:01:59 -04:00
when " progressAsc "
@ sortedMembers . sort (a, b) =>
for levelID , level of @ campaign . get ( ' levels ' )
2015-10-06 14:20:53 -04:00
if @ userLevelStateMap [ a ] ? [ levelID ] isnt ' complete ' and @ userLevelStateMap [ b ] ? [ levelID ] is ' complete '
2015-09-13 01:01:59 -04:00
return - 1
2015-10-06 14:20:53 -04:00
else if @ userLevelStateMap [ a ] ? [ levelID ] is ' complete ' and @ userLevelStateMap [ b ] ? [ levelID ] isnt ' complete '
2015-09-13 01:01:59 -04:00
return 1
0
when " progressDesc "
@ sortedMembers . sort (a, b) =>
for levelID , level of @ campaign . get ( ' levels ' )
2015-10-06 14:20:53 -04:00
if @ userLevelStateMap [ a ] ? [ levelID ] isnt ' complete ' and @ userLevelStateMap [ b ] ? [ levelID ] is ' complete '
2015-09-13 01:01:59 -04:00
return 1
2015-10-06 14:20:53 -04:00
else if @ userLevelStateMap [ a ] ? [ levelID ] is ' complete ' and @ userLevelStateMap [ b ] ? [ levelID ] isnt ' complete '
2015-09-13 01:01:59 -04:00
return - 1
0
else
2015-10-09 12:27:30 -04:00
@ sortedMembers . sort (a, b) =>
aName = @ memberUserMap [ a ] ? . get ( ' name ' ) ? ' Anoner '
bName = @ memberUserMap [ b ] ? . get ( ' name ' ) ? ' Anoner '
aName . localeCompare ( bName )
2015-10-27 20:47:48 -04:00
getOwnerName: ->
2015-12-01 17:15:18 -05:00
return if @ owner . isNew ( )
2015-10-27 20:47:48 -04:00
if @ owner . get ( ' firstName ' ) and @ owner . get ( ' lastName ' )
return " #{ @ owner . get ( ' firstName ' ) } #{ @ owner . get ( ' lastName ' ) } "
2015-12-01 17:15:18 -05:00
@ owner . get ( ' name ' ) or @ owner . get ( ' email ' )