hook up extension checkboxes for ember quizzes moderate page
fixes CNVS-12450 test plan: - as a teacher - enable fabulous quizzes - enroll a few students in the course - create a quiz with both a time limit and a limited number of attempts - visit the quiz moderate page - click the "edit" icon for a student - the modal dialog should continue to work as described in g/35089 - use the checkboxes to select multiple students at once. - clicking the header row checkbox should toggle selection of all checkboxes - deselecting all student checkboxes should automatically deselect the header one - after making a selection with the checkboxes - a new link appears to change extensions for the selected students - click this link to bring up the extensions modal dialog - make sure that changing the extensions from here updates the extensions for all selected students. - there is also a new "unlocked" icon that shows up when you set the manually unlocked status for a student. make sure that when this setting is updated that the lock icon appears. Change-Id: Ibc55087a99d02fe3da7f9390e07a1f0bb5ccca35 Reviewed-on: https://gerrit.instructure.com/35228 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Ahmad Amireh <ahmad@instructure.com> QA-Review: Caleb Guanzon <cguanzon@instructure.com> Product-Review: Derek DeVries <ddevries@instructure.com>
This commit is contained in:
parent
3181ac8b11
commit
8347e0a9a3
|
@ -0,0 +1,74 @@
|
|||
define [
|
||||
'ember'
|
||||
'ic-ajax'
|
||||
'i18n!quiz_extensions'
|
||||
], (Em, ajax, I18n) ->
|
||||
|
||||
StudentExtensionsController = Em.ArrayController.extend
|
||||
needs: ['quiz']
|
||||
quiz: Ember.computed.alias('controllers.quiz.model')
|
||||
extension: Ember.Object.create
|
||||
|
||||
studentExtensionTitle:
|
||||
I18n.t 'student_extensions', 'Student Extensions'
|
||||
|
||||
extensionsFor: ( ->
|
||||
if @get("length") == 1
|
||||
student = @get("firstObject.name")
|
||||
I18n.t('extensions_student', 'Extensions for %{student}', student: student)
|
||||
else
|
||||
I18n.t('extensions_num_students', 'Extensions for %{num} Students', num: @get('length'))
|
||||
).property('@each')
|
||||
|
||||
modalHeight: ( ->
|
||||
height = 220
|
||||
height += 60 if @get('quiz.timeLimit')
|
||||
height += 60 if !@get('unlimitedAttempts')
|
||||
height
|
||||
).property('quiz.timeLimit', 'unlimitedAttempts')
|
||||
|
||||
unlimitedAttempts: ( ->
|
||||
@get('quiz.multipleAttemptsAllowed') && @get('quiz.allowedAttempts') == -1
|
||||
).property('quiz.multipleAttemptsAllowed', 'quiz.allowedAttempts')
|
||||
|
||||
quizExtraAttemptsNote: ( ->
|
||||
I18n.t('everyone_gets_attempts', 'everyone already gets %{num}', num: @get('quiz.allowedAttempts'))
|
||||
).property('quiz.allowedAttempts')
|
||||
|
||||
quizExtraTimeNote: ( ->
|
||||
I18n.t('everyone_gets_time', 'everyone already gets %{num} minutes', num: @get('quiz.timeLimit'))
|
||||
).property('quiz.timeLimit')
|
||||
|
||||
# setup the extension object with defaults based on the selected students
|
||||
setupExtension: ( ->
|
||||
if @get('length') == 1
|
||||
qs = @get("firstObject.quizSubmission")
|
||||
@set("extension.extraAttempts", qs.get("extraAttempts"))
|
||||
@set("extension.extraTime", qs.get("extraTime"))
|
||||
@set("extension.manuallyUnlocked", qs.get("manuallyUnlocked"))
|
||||
else
|
||||
allUnlocked = @everyProperty('quizSubmission.manuallyUnlocked', true)
|
||||
@set("extension.manuallyUnlocked", !!@get('length') && allUnlocked)
|
||||
).observes('model')
|
||||
|
||||
actions:
|
||||
submitStudentExtensions: ->
|
||||
quizExtensions = @get("model").map (student) =>
|
||||
user_id: student.get("id")
|
||||
extra_attempts: @get('extension.extraAttempts')
|
||||
extra_time: @get('extension.extraTime')
|
||||
manually_unlocked: @get('extension.manuallyUnlocked')
|
||||
|
||||
options =
|
||||
url: @get("quiz").get('quizExtensionsUrl')
|
||||
data: JSON.stringify(quiz_extensions: quizExtensions)
|
||||
type: 'POST'
|
||||
dataType: 'json'
|
||||
contentType: 'application/json'
|
||||
headers: 'Accepts': 'application/vnd.api+json'
|
||||
|
||||
ajax.raw(options).then =>
|
||||
$.flashMessage I18n.t 'extensions_successfully_added', 'Extensions Successfully Added'
|
||||
@send 'refreshData'
|
||||
|
||||
|
|
@ -20,91 +20,30 @@ define [
|
|||
@send('refreshData')
|
||||
Ember.run.later this, @triggerReload, LATER_REFRESH_MS
|
||||
|
||||
needs: ['quiz']
|
||||
checkedStudents: ( ->
|
||||
@filterProperty('isChecked', true)
|
||||
).property('@each.isChecked')
|
||||
|
||||
quiz: Ember.computed.alias('controllers.quiz.model')
|
||||
|
||||
# student extension modal
|
||||
|
||||
selectedStudents: []
|
||||
|
||||
studentExtensionTitle:
|
||||
I18n.t 'student_extensions', 'Student Extensions'
|
||||
|
||||
singleSelection: ( ->
|
||||
@get("selectedStudents.length") == 1
|
||||
).property('selectedStudents')
|
||||
|
||||
extensionsFor: ( ->
|
||||
if @get("singleSelection")
|
||||
student = @get("selectedStudents.firstObject.name")
|
||||
I18n.t('extensions_student', 'Extensions for %{student}', student: student)
|
||||
allChecked: ( (key, value) ->
|
||||
if value == undefined
|
||||
!!@get('length') && @everyProperty('isChecked', true)
|
||||
else
|
||||
I18n.t('extensions_num_students', 'Extensions for %{num} Students', num: @get('length'))
|
||||
).property('selectedStudents')
|
||||
@setEach('isChecked', value)
|
||||
).property('@each.isChecked')
|
||||
|
||||
modalHeight: ( ->
|
||||
height = 220
|
||||
height += 60 if @get('quiz.timeLimit')
|
||||
height += 60 if !@get('unlimitedAttempts')
|
||||
height
|
||||
).property('quiz.timeLimit', 'unlimitedAttempts')
|
||||
changeExtensionFor: ( ->
|
||||
I18n.t('change_extension_for', "Change extension for %{num} Students", num: @get("checkedStudents.length"))
|
||||
).property('checkedStudents')
|
||||
|
||||
unlimitedAttempts: ( ->
|
||||
@get('quiz.multipleAttemptsAllowed') && @get('quiz.allowedAttempts') == -1
|
||||
).property('quiz.multipleAttemptsAllowed', 'quiz.allowedAttempts')
|
||||
|
||||
quizExtraAttemptsNote: ( ->
|
||||
I18n.t('everyone_gets_attempts', 'everyone already gets %{num}', num: @get('quiz.allowedAttempts'))
|
||||
).property('quiz.allowedAttempts')
|
||||
|
||||
quizExtraTimeNote: ( ->
|
||||
I18n.t('everyone_gets_time', 'everyone already gets %{num} minutes', num: @get('quiz.timeLimit'))
|
||||
).property('quiz.timeLimit')
|
||||
|
||||
extraAttempts: ( ->
|
||||
if @get("singleSelection")
|
||||
student = @get("selectedStudents").get("firstObject")
|
||||
student.get("quizSubmission").get("extraAttempts")
|
||||
).property('selectedStudents')
|
||||
|
||||
extraTime: ( ->
|
||||
if @get("singleSelection")
|
||||
student = @get("selectedStudents").get("firstObject")
|
||||
student.get("quizSubmission").get("extraTime")
|
||||
).property('selectedStudents')
|
||||
|
||||
manuallyUnlocked: ( ->
|
||||
if @get("singleSelection")
|
||||
student = @get("selectedStudents").get("firstObject")
|
||||
student.get("quizSubmission").get("manuallyUnlocked")
|
||||
).property('selectedStudents')
|
||||
|
||||
# /student extension modal
|
||||
studentsHaveTakenQuiz: ( ->
|
||||
complete = @filterProperty('quizSubmission.isComplete', true).get("length")
|
||||
total = @get("length")
|
||||
I18n.t('students_have_taken', '%{complete} of %{total} students have completed this quiz', complete: complete, total: total)
|
||||
).property('@each.quizSubmission.isComplete')
|
||||
|
||||
actions:
|
||||
refreshData: ->
|
||||
@set('reloading', true)
|
||||
true
|
||||
|
||||
# student extension modal
|
||||
submitStudentExtensions: ->
|
||||
quizExtensions = @get("selectedStudents").map (student) =>
|
||||
user_id: student.get("id")
|
||||
extra_attempts: @get('extraAttempts')
|
||||
extra_time: @get('extraTime')
|
||||
manually_unlocked: @get('manuallyUnlocked')
|
||||
|
||||
options =
|
||||
url: @get("quiz").get('quizExtensionsUrl')
|
||||
data: JSON.stringify(quiz_extensions: quizExtensions)
|
||||
type: 'POST'
|
||||
dataType: 'json'
|
||||
contentType: 'application/json'
|
||||
headers: 'Accepts': 'application/vnd.api+json'
|
||||
|
||||
ajax.raw(options).then =>
|
||||
$.flashMessage I18n.t 'extensions_successfully_added', 'Extensions Successfully Added'
|
||||
@send 'refreshData'
|
||||
|
||||
QuizModerateController
|
||||
|
|
|
@ -53,11 +53,13 @@ define [
|
|||
@forceReload(quiz)
|
||||
|
||||
studentExtension: (model) ->
|
||||
controller = @controllerFor('quiz.moderate')
|
||||
controller.set('selectedStudents', Em.A([model]))
|
||||
model = if model.get("length") then model else [model]
|
||||
controller = @controllerFor('quiz.moderate.student_extension')
|
||||
controller.set('model', Em.A(model))
|
||||
|
||||
@render 'quiz/moderate/student_extension',
|
||||
into: 'application'
|
||||
outlet: 'modal'
|
||||
controller: controller
|
||||
|
||||
ModerateRoute
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
<div class="quiz-moderate allow-inputs">
|
||||
<div class="clearfix">
|
||||
<strong class="have-taken">{{studentsHaveTakenQuiz}}</strong>
|
||||
{{#if checkedStudents.length}}
|
||||
<a class="change-extensions-for icon-edit" href="#" {{action 'studentExtension' checkedStudents}}>
|
||||
{{changeExtensionFor}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<table class="table table-striped quiz-table" id="moderate-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="check">{{input type='checkbox' checked=headerChecked}}</th>
|
||||
<th class="check">{{input type='checkbox' checked=allChecked}}</th>
|
||||
<th class="name">
|
||||
{{#t 'student'}}Student{{/t}}
|
||||
</th>
|
||||
|
@ -18,7 +26,8 @@
|
|||
<th>
|
||||
{{#t 'score'}}Score{{/t}}
|
||||
</th>
|
||||
<th>
|
||||
<th class="unlocked"> </th>
|
||||
<th class="edit">
|
||||
<a {{bind-attr class="reloading::icon-refresh"}} {{action 'refreshData'}} href>
|
||||
<img {{bind-attr class=":icon-refreshing reloading::inactive" }}
|
||||
src='/images/ajax-reload-animated.gif' />
|
||||
|
@ -31,7 +40,7 @@
|
|||
<tbody>
|
||||
{{#each user in controller itemController='quiz.submission_row'}}
|
||||
<tr class='moderate-submission-row'>
|
||||
<td class="check">{{input type='checkbox' checked=selected}}</td>
|
||||
<td class="check">{{input type='checkbox' checked=isChecked}}</td>
|
||||
<td class="name">
|
||||
{{#if hasActiveSubmission}}
|
||||
<a {{bind-attr href='historyLink'}}>
|
||||
|
@ -57,7 +66,16 @@
|
|||
|
||||
<td>{{remainingStatusLabel}}{{remainingAttempts}}</td>
|
||||
<td>{{user.friendlyScore}}</td>
|
||||
<td>
|
||||
<td class="unlocked">
|
||||
{{#if quizSubmission.manuallyUnlocked}}
|
||||
<span class="unlocked" title="{{#t 'manually_unlocked'}}Manually Unlocked{{/t}}">
|
||||
<i class="icon-unlock standalone-icon">
|
||||
<span class="screenreader-only">{{#t 'manually_unlocked'}}Manually Unlocked{{/t}}</span>
|
||||
</i>
|
||||
</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td class="edit">
|
||||
<a class="icon-edit" href="#" {{action 'studentExtension' user}}>
|
||||
<span class="screenreader-only">{{#t 'edit'}}Edit{{/t}}</span>
|
||||
</a>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<em class="note">{{quizExtraAttemptsNote}}</em>
|
||||
</label>
|
||||
<span class="input">
|
||||
{{input type="text" id="extra_attempts" value=extraAttempts class="text"}}
|
||||
{{input type="text" id="extra_attempts" value=extension.extraAttempts class="text"}}
|
||||
<span class="units">{{t 'attempts' 'attempts'}}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -27,14 +27,14 @@
|
|||
<em class="note">{{quizExtraTimeNote}}</em>
|
||||
</label>
|
||||
<span class="input">
|
||||
{{input type="text" id="extra_time" value=extraTime class="text"}}
|
||||
{{input type="text" id="extra_time" value=extension.extraTime class="text"}}
|
||||
<span class="units">{{t 'minutes' 'minutes'}}</span>
|
||||
</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="field manually-unlock">
|
||||
{{input type="checkbox" id="manually_unlocked" checked=manuallyUnlocked}}
|
||||
{{input type="checkbox" id="manually_unlocked" checked=extension.manuallyUnlocked}}
|
||||
<label for="manually_unlocked">
|
||||
{{t 'manually_unlock' 'Manually unlock the quiz for the next attempt'}}
|
||||
</label>
|
||||
|
|
|
@ -1,3 +1,64 @@
|
|||
#quiz-show {
|
||||
.quiz-moderate {
|
||||
.have-taken {
|
||||
float: left;
|
||||
margin-bottom: 20px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.change-extensions-for {
|
||||
float: right;
|
||||
margin-bottom: 20px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
#moderate-table {
|
||||
.check {
|
||||
width: 12px;
|
||||
line-height: 1em;
|
||||
vertical-align: top;
|
||||
}
|
||||
.icon-refreshing {
|
||||
&.inactive {
|
||||
display:none;
|
||||
}
|
||||
position: relative;
|
||||
left: -2px;
|
||||
top: -2px;
|
||||
}
|
||||
th {
|
||||
text-align: center;
|
||||
}
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
th.name,
|
||||
td.name {
|
||||
text-align: left;
|
||||
}
|
||||
th.unlocked,
|
||||
td.unlocked {
|
||||
padding: 8px 2px;
|
||||
}
|
||||
th.edit,
|
||||
td.edit {
|
||||
padding: 8px 2px;
|
||||
}
|
||||
th a.icon-refresh:before {
|
||||
font-size: 100%;
|
||||
}
|
||||
th a.icon-refresh,
|
||||
td a.icon-edit {
|
||||
color: #444;
|
||||
}
|
||||
.extra-time {
|
||||
font-size: 85%;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#student-extension-dialog {
|
||||
h2 {
|
||||
margin: 0 0 1.5em 0;
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
|
||||
.tabs-content {
|
||||
border-top: 1px solid #CAD0D7;
|
||||
padding: 55px 35px;
|
||||
padding: 25px 35px 55px 35px;
|
||||
}
|
||||
|
||||
table.quiz-table {
|
||||
|
@ -168,49 +168,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
//===== Moderate Tab ===
|
||||
|
||||
.quiz-moderate {
|
||||
|
||||
#moderate-table {
|
||||
.check {
|
||||
width: 12px;
|
||||
line-height: 1em;
|
||||
vertical-align: top;
|
||||
}
|
||||
.icon-refreshing {
|
||||
&.inactive {
|
||||
display:none;
|
||||
}
|
||||
position: relative;
|
||||
left: -2px;
|
||||
top: -2px;
|
||||
}
|
||||
th {
|
||||
text-align: center;
|
||||
}
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
th.name,
|
||||
td.name {
|
||||
text-align: left;
|
||||
}
|
||||
th a.icon-refresh:before {
|
||||
font-size: 100%;
|
||||
}
|
||||
th a.icon-refresh,
|
||||
td a.icon-edit {
|
||||
color: #444;
|
||||
}
|
||||
.extra-time {
|
||||
font-size: 85%;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ic-actions overrides
|
||||
|
||||
|
@ -234,6 +191,7 @@
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
// Dialogs - Put outside the #quiz_show scope because jQueryUI puts
|
||||
// it in the body.
|
||||
|
||||
|
|
Loading…
Reference in New Issue