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:
Derek DeVries 2014-05-07 14:09:13 -06:00
parent 3181ac8b11
commit 8347e0a9a3
7 changed files with 182 additions and 130 deletions

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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">&nbsp;</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>

View File

@ -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>

View File

@ -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;

View File

@ -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.