Merge branch 'MDL-48771-squashed' of git://github.com/timhunt/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Tue, 4 Apr 2017 02:37:26 +0000 (10:37 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Tue, 4 Apr 2017 02:37:26 +0000 (10:37 +0800)
14 files changed:
mod/quiz/classes/output/edit_renderer.php
mod/quiz/edit_rest.php
mod/quiz/lang/en/quiz.php
mod/quiz/styles.css
mod/quiz/tests/behat/editing_remove_multiple_questions.feature [new file with mode: 0644]
mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-debug.js
mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-min.js
mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate.js
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js
mod/quiz/yui/src/repaginate/js/repaginate.js
mod/quiz/yui/src/toolboxes/js/resource.js
mod/quiz/yui/src/toolboxes/js/toolbox.js

index 60c9c42..238a1a8 100644 (file)
@@ -59,11 +59,21 @@ class edit_renderer extends \plugin_renderer_base {
 
         // Information at the top.
         $output .= $this->quiz_state_warnings($structure);
+
+        $output .= html_writer::start_div('mod_quiz-edit-top-controls');
         $output .= $this->quiz_information($structure);
         $output .= $this->maximum_grade_input($structure, $pageurl);
+
+        $output .= html_writer::start_div('mod_quiz-edit-action-buttons btn-group edit-toolbar', ['role' => 'group']);
         $output .= $this->repaginate_button($structure, $pageurl);
+        $output .= $this->selectmultiple_button($structure);
+        $output .= html_writer::end_tag('div');
+
         $output .= $this->total_marks($quizobj->get_quiz());
 
+        $output .= $this->selectmultiple_controls($structure);
+        $output .= html_writer::end_tag('div');
+
         // Show the questions organised into sections and pages.
         $output .= $this->start_section_list($structure);
 
@@ -187,12 +197,6 @@ class edit_renderer extends \plugin_renderer_base {
 
         $header = html_writer::tag('span', get_string('repaginatecommand', 'quiz'), array('class' => 'repaginatecommand'));
         $form = $this->repaginate_form($structure, $pageurl);
-        $containeroptions = array(
-                'class'  => 'rpcontainerclass',
-                'cmid'   => $structure->get_cmid(),
-                'header' => $header,
-                'form'   => $form,
-        );
 
         $buttonoptions = array(
             'type'  => 'submit',
@@ -200,6 +204,8 @@ class edit_renderer extends \plugin_renderer_base {
             'id'    => 'repaginatecommand',
             'value' => get_string('repaginatecommand', 'quiz'),
             'class' => 'btn btn-secondary m-b-1',
+            'data-header' => $header,
+            'data-form'   => $form,
         );
         if (!$structure->can_be_repaginated()) {
             $buttonoptions['disabled'] = 'disabled';
@@ -207,8 +213,89 @@ class edit_renderer extends \plugin_renderer_base {
             $this->page->requires->yui_module('moodle-mod_quiz-repaginate', 'M.mod_quiz.repaginate.init');
         }
 
-        return html_writer::tag('div',
-                html_writer::empty_tag('input', $buttonoptions), $containeroptions);
+        return html_writer::empty_tag('input', $buttonoptions);
+    }
+
+    /**
+     * Generate the bulk action button.
+     *
+     * @param structure $structure the structure of the quiz being edited.
+     * @return string HTML to output.
+     */
+    protected function selectmultiple_button(structure $structure) {
+        $buttonoptions = array(
+            'type'  => 'button',
+            'name'  => 'selectmultiple',
+            'id'    => 'selectmultiplecommand',
+            'value' => get_string('selectmultipleitems', 'quiz'),
+            'class' => 'btn btn-secondary m-b-1'
+        );
+        if (!$structure->can_be_edited()) {
+            $buttonoptions['disabled'] = 'disabled';
+        }
+
+        return html_writer::tag('button', get_string('selectmultipleitems', 'quiz'), $buttonoptions);
+    }
+
+    /**
+     * Generate the controls that appear when the bulk action button is pressed.
+     *
+     * @param structure $structure the structure of the quiz being edited.
+     * @return string HTML to output.
+     */
+    protected function selectmultiple_controls(structure $structure) {
+        $output = '';
+
+        // Bulk action button delete and bulk action button cancel.
+        $buttondeleteoptions = array(
+            'type' => 'button',
+            'id' => 'selectmultipledeletecommand',
+            'value' => get_string('deleteselected', 'mod_quiz'),
+            'class' => 'btn btn-secondary'
+        );
+        $buttoncanceloptions = array(
+            'type' => 'button',
+            'id' => 'selectmultiplecancelcommand',
+            'value' => get_string('cancel', 'moodle'),
+            'class' => 'btn btn-secondary'
+        );
+
+        $groupoptions = array(
+            'class' => 'btn-group selectmultiplecommand actions',
+            'role' => 'group'
+        );
+
+        $output .= html_writer::tag('div',
+                        html_writer::tag('button', get_string('deleteselected', 'mod_quiz'), $buttondeleteoptions) .
+                        " " .
+                        html_writer::tag('button', get_string('cancel', 'moodle'),
+                $buttoncanceloptions), $groupoptions);
+
+        $toolbaroptions = array(
+            'class' => 'btn-toolbar',
+            'role' => 'toolbar',
+            'aria-label' => get_string('selectmultipletoolbar', 'quiz'),
+        );
+
+        // Select all/deselect all questions.
+        $buttonselectalloptions = array(
+            'role' => 'button',
+            'id' => 'questionselectall',
+            'class' => 'btn btn-link'
+        );
+        $buttondeselectalloptions = array(
+            'role' => 'button',
+            'id' => 'questiondeselectall',
+            'class' => 'btn btn-link'
+        );
+        $output .= html_writer::tag('div',
+                html_writer::tag('div',
+                        html_writer::link('#', get_string('selectall', 'quiz'), $buttonselectalloptions) .
+                        html_writer::tag('span', "/", ['class' => 'separator']) .
+                        html_writer::link('#', get_string('selectnone', 'quiz'), $buttondeselectalloptions),
+                        array('class' => 'btn-group selectmultiplecommandbuttons')),
+                $toolbaroptions);
+        return $output;
     }
 
     /**
@@ -640,6 +727,10 @@ class edit_renderer extends \plugin_renderer_base {
         }
 
         $output .= html_writer::start_div('mod-indent-outer');
+        $output .= html_writer::tag('input', '', array('id' => 'selectquestion-' .
+                $structure->get_displayed_number_for_slot($slot), 'name' => 'selectquestion[]',
+               'type' => 'checkbox', 'class' => 'select-multiple-checkbox',
+               'value' => $structure->get_displayed_number_for_slot($slot)));
         $output .= $this->question_number($structure->get_displayed_number_for_slot($slot));
 
         // This div is used to indent the content.
@@ -1024,6 +1115,7 @@ class edit_renderer extends \plugin_renderer_base {
         unset($config->pagehtml);
         unset($config->addpageiconhtml);
 
+        $this->page->requires->strings_for_js(array('areyousureremoveselected'), 'quiz');
         $this->page->requires->yui_module('moodle-mod_quiz-toolboxes',
                 'M.mod_quiz.init_section_toolbox',
                 array(array(
index 6ad7fc2..f06ca37 100644 (file)
@@ -47,6 +47,7 @@ $maxmark    = optional_param('maxmark', '', PARAM_FLOAT);
 $newheading = optional_param('newheading', '', PARAM_TEXT);
 $shuffle    = optional_param('newshuffle', 0, PARAM_INT);
 $page       = optional_param('page', '', PARAM_INT);
+$ids        = optional_param('ids', '', PARAM_SEQUENCE);
 $PAGE->set_url('/mod/quiz/edit-rest.php',
         array('quizid' => $quizid, 'class' => $class));
 
@@ -62,6 +63,9 @@ $modcontext = context_module::instance($cm->id);
 
 echo $OUTPUT->header(); // Send headers.
 
+// All these AJAX actions should be logically atomic.
+$transaction = $DB->start_delegated_transaction();
+
 // OK, now let's process the parameters and do stuff
 // MDL-10221 the DELETE method is not allowed on some web servers,
 // so we simulate it with the action URL param.
@@ -70,6 +74,8 @@ if ($pageaction == 'DELETE') {
     $requestmethod = 'DELETE';
 }
 
+$result = null;
+
 switch($requestmethod) {
     case 'POST':
     case 'GET': // For debugging.
@@ -80,17 +86,17 @@ switch($requestmethod) {
                 switch ($field) {
                     case 'getsectiontitle':
                         require_capability('mod/quiz:manage', $modcontext);
-                        echo json_encode(array('instancesection' => $section->heading));
+                        $result = array('instancesection' => $section->heading);
                         break;
                     case 'updatesectiontitle':
                         require_capability('mod/quiz:manage', $modcontext);
                         $structure->set_section_heading($id, $newheading);
-                        echo json_encode(array('instancesection' => format_string($newheading)));
+                        $result = array('instancesection' => format_string($newheading));
                         break;
                     case 'updateshufflequestions':
                         require_capability('mod/quiz:manage', $modcontext);
                         $structure->set_section_shuffle($id, $shuffle);
-                        echo json_encode(array('instanceshuffle' => $section->shufflequestions));
+                        $result = array('instanceshuffle' => $section->shufflequestions);
                         break;
                 }
                 break;
@@ -108,14 +114,13 @@ switch($requestmethod) {
                         }
                         $structure->move_slot($id, $previousid, $page);
                         quiz_delete_previews($quiz);
-                        echo json_encode(array('visible' => true));
+                        $result = array('visible' => true);
                         break;
 
                     case 'getmaxmark':
                         require_capability('mod/quiz:manage', $modcontext);
                         $slot = $DB->get_record('quiz_slots', array('id' => $id), '*', MUST_EXIST);
-                        echo json_encode(array('instancemaxmark' =>
-                                quiz_format_question_grade($quiz, $slot->maxmark)));
+                        $result = array('instancemaxmark' => quiz_format_question_grade($quiz, $slot->maxmark));
                         break;
 
                     case 'updatemaxmark':
@@ -129,8 +134,8 @@ switch($requestmethod) {
                             quiz_update_all_final_grades($quiz);
                             quiz_update_grades($quiz, 0, true);
                         }
-                        echo json_encode(array('instancemaxmark' => quiz_format_question_grade($quiz, $maxmark),
-                                'newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades)));
+                        $result = array('instancemaxmark' => quiz_format_question_grade($quiz, $maxmark),
+                                'newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades));
                         break;
 
                     case 'updatepagebreak':
@@ -141,7 +146,25 @@ switch($requestmethod) {
                             $json[$slot->slot] = array('id' => $slot->id, 'slot' => $slot->slot,
                                                             'page' => $slot->page);
                         }
-                        echo json_encode(array('slots' => $json));
+                        $result = array('slots' => $json);
+                        break;
+
+                    case 'deletemultiple':
+                        require_capability('mod/quiz:manage', $modcontext);
+
+                        $ids = explode(',', $ids);
+                        foreach ($ids as $id) {
+                            $slot = $DB->get_record('quiz_slots', array('quizid' => $quiz->id, 'id' => $id),
+                                    '*', MUST_EXIST);
+                            if (quiz_has_question_use($quiz, $slot->slot)) {
+                                $structure->remove_slot($slot->slot);
+                            }
+                        }
+                        quiz_delete_previews($quiz);
+                        quiz_update_sumgrades($quiz);
+
+                        $result = array('newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades),
+                                'deleted' => true, 'newnumquestions' => $structure->get_question_count());
                         break;
 
                     case 'updatedependency':
@@ -149,7 +172,7 @@ switch($requestmethod) {
                         $slot = $structure->get_slot_by_id($id);
                         $value = (bool) $value;
                         $structure->update_question_dependency($slot->id, $value);
-                        echo json_encode(array('requireprevious' => $value));
+                        $result = array('requireprevious' => $value);
                         break;
                 }
                 break;
@@ -161,7 +184,7 @@ switch($requestmethod) {
             case 'section':
                 require_capability('mod/quiz:manage', $modcontext);
                 $structure->remove_section_heading($id);
-                echo json_encode(array('deleted' => true));
+                $result = array('deleted' => true);
                 break;
 
             case 'resource':
@@ -172,9 +195,12 @@ switch($requestmethod) {
                 $structure->remove_slot($slot->slot);
                 quiz_delete_previews($quiz);
                 quiz_update_sumgrades($quiz);
-                echo json_encode(array('newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades),
-                            'deleted' => true, 'newnumquestions' => $structure->get_question_count()));
+                $result = array('newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades),
+                            'deleted' => true, 'newnumquestions' => $structure->get_question_count());
                 break;
         }
         break;
 }
+
+$transaction->allow_commit();
+echo json_encode($result);
index d360884..fac4f4f 100644 (file)
@@ -816,6 +816,8 @@ $string['select'] = 'Select';
 $string['selectall'] = 'Select all';
 $string['selectcategory'] = 'Select category';
 $string['selectedattempts'] = 'Selected attempts...';
+$string['selectmultipleitems'] = 'Select multiple items';
+$string['selectmultipletoolbar'] = 'Select multiple toolbar';
 $string['selectnone'] = 'Deselect all';
 $string['selectquestiontype'] = '-- Select question type --';
 $string['serveradded'] = 'Server added';
index 5395348..37316e7 100644 (file)
@@ -633,10 +633,18 @@ table.quizreviewsummary td.cell {
     margin: 4px 0;
 }
 
+#page-mod-quiz-edit .mod_quiz-edit-top-controls {
+    position: relative;
+}
+#page-mod-quiz-edit .mod_quiz-edit-action-buttons {
+    display: block;
+    min-height: 2.85em;
+}
+
 #page-mod-quiz-edit .maxgrade,
 #page-mod-quiz-edit .totalpoints {
-    display: block;
-    float: right;
+    position: absolute;
+    right: 0;
     margin: -2.85em 0 0;
     padding: .2em;
 }
@@ -644,6 +652,9 @@ table.quizreviewsummary td.cell {
 #page-mod-quiz-edit .maxgrade label {
     display: inline;
 }
+#page-mod-quiz-edit .maxgrade input[type="submit"] {
+    margin: 0;
+}
 
 #page-mod-quiz-edit li.activity > div,
 #page-mod-quiz-edit li.pagenumber {
@@ -1117,6 +1128,82 @@ table#categoryquestions {
     background-color: #fff;
 }
 
+/* Bulk edit actions */
+
+.selectmultiplecommandbuttons {
+    margin: 0.6em 0.4em;
+}
+
+.btn-group.selectmultiplecommand,
+.btn-group.selectmultiplecommandbuttons,
+.select-multiple-checkbox {
+    display: none;
+}
+
+#page-mod-quiz-edit.select-multiple .selectmultiplecommand,
+#page-mod-quiz-edit.select-multiple .selectmultiplecommandbuttons,
+#page-mod-quiz-edit.select-multiple .select-multiple-checkbox {
+    display: inherit;
+}
+
+#page-mod-quiz-edit.select-multiple .selectmultiplecommandbuttons .separator {
+    position: relative;
+    float: left;
+    padding: .5rem 0;
+}
+
+#page-mod-quiz-edit #questionselectall {
+    padding-right: .1rem;
+}
+
+#page-mod-quiz-edit #questiondeselectall {
+    padding-left: .1rem;
+}
+
+#page-mod-quiz-edit.select-multiple input.select-multiple-checkbox[type="checkbox"] {
+    display: inline;
+}
+
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section .activity .editing_move,
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section .activity .commands {
+    display: none;
+}
+
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section .page_split_join_wrapper {
+    display: none;
+}
+
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section .activity .actions .editing_delete,
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section .activity .actions .editing_maxmark {
+    display: none;
+}
+
+#page-mod-quiz-edit.select-multiple#page-mod-quiz-edit .maxgrade,
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .last-add-menu {
+    display: none;
+}
+
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section-heading,
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section-heading form,
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section-heading .instancesectioncontainer,
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section-heading .instanceshufflequestions,
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .section-heading .instancesectioncontainer h3 {
+    display: none;
+}
+
+#page-mod-quiz-edit.select-multiple .mod-quiz-edit-content .edit-toolbar .m-b-1 {
+    display: none;
+}
+
+#page-mod-quiz-edit.select-multiple#page-mod-quiz-edit ul.slots li.section li.activity .mod-indent-outer {
+    padding-left: 3px;
+}
+
+.section .summary .iconsmall,
+.section .activity .iconsmall {
+    float: left;
+}
+
 /* Base theme needs extra support. */
 #page-mod-quiz-edit ul.slots li.section ul.section {
     list-style: none;
@@ -1178,18 +1265,3 @@ table#categoryquestions {
         page-break-inside: avoid;
     }
 }
-/* Ajustments for mobile devices */
-
-@media only screen and (max-width: 565px) {
-    #page-mod-quiz-edit .rpcontainerclass {
-        margin-top: 3em;
-    }
-
-    #page-mod-quiz-edit .maxgrade {
-        margin-top: 0.1em;
-    }
-
-    #page-mod-quiz-edit .statusbar {
-        padding: 0;
-    }
-}
diff --git a/mod/quiz/tests/behat/editing_remove_multiple_questions.feature b/mod/quiz/tests/behat/editing_remove_multiple_questions.feature
new file mode 100644 (file)
index 0000000..961f106
--- /dev/null
@@ -0,0 +1,170 @@
+@mod @mod_quiz
+Feature: Edit quiz page - remove multiple questions
+  In order to change the layout of a quiz I built efficiently
+  As a teacher
+  I need to be able to delete many questions questions.
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email                |
+      | teacher1 | T1        | Teacher1 | teacher1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1        | 0        |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+    And the following "question categories" exist:
+      | contextlevel | reference | name           |
+      | Course       | C1        | Test questions |
+    And the following "activities" exist:
+      | activity   | name   | course | idnumber |
+      | quiz       | Quiz 1 | C1     | quiz1    |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Quiz 1"
+
+  @javascript
+  Scenario: Delete selected question using select multiple items feature.
+    Given the following "questions" exist:
+      | questioncategory | qtype     | name       | questiontext        |
+      | Test questions   | truefalse | Question A | This is question 01 |
+      | Test questions   | truefalse | Question B | This is question 02 |
+      | Test questions   | truefalse | Question C | This is question 03 |
+    And quiz "Quiz 1" contains the following questions:
+      | question   | page |
+      | Question A | 1    |
+      | Question B | 1    |
+      | Question C | 2    |
+    And I navigate to "Edit quiz" in current page administration
+
+    # Confirm the starting point.
+    Then I should see "Question A" on quiz page "1"
+    And I should see "Question B" on quiz page "1"
+    And I should see "Question C" on quiz page "2"
+    And I should see "Total of marks: 3.00"
+    And I should see "Questions: 3"
+    And I should see "This quiz is open"
+
+    # Delete last question in last page. Page contains multiple questions. No reordering.
+    When I click on "Select multiple items" "button"
+    Then I click on "selectquestion-3" "checkbox"
+    And I click on "Delete selected" "button"
+    And I click on "Yes" "button" in the "Confirm" "dialogue"
+
+    Then I should see "Question A" on quiz page "1"
+    And I should see "Question B" on quiz page "1"
+    And I should not see "Question C" on quiz page "2"
+    And I should see "Total of marks: 2.00"
+    And I should see "Questions: 2"
+
+  @javascript
+  Scenario: Delete first selected question using select multiple items feature.
+    Given the following "questions" exist:
+      | questioncategory | qtype     | name       | questiontext        |
+      | Test questions   | truefalse | Question A | This is question 01 |
+      | Test questions   | truefalse | Question B | This is question 02 |
+      | Test questions   | truefalse | Question C | This is question 03 |
+    And quiz "Quiz 1" contains the following questions:
+      | question   | page |
+      | Question A | 1    |
+      | Question B | 2    |
+      | Question C | 2    |
+    And I navigate to "Edit quiz" in current page administration
+
+  # Confirm the starting point.
+    Then I should see "Question A" on quiz page "1"
+    And I should see "Question B" on quiz page "2"
+    And I should see "Question C" on quiz page "2"
+    And I should see "Total of marks: 3.00"
+    And I should see "Questions: 3"
+    And I should see "This quiz is open"
+
+  # Delete first question in first page. Page contains multiple questions. No reordering.
+    When I click on "Select multiple items" "button"
+    Then I click on "selectquestion-1" "checkbox"
+    And I click on "Delete selected" "button"
+    And I click on "Yes" "button" in the "Confirm" "dialogue"
+
+    Then I should not see "Question A" on quiz page "1"
+    And I should see "Question B" on quiz page "1"
+    And I should see "Question C" on quiz page "1"
+    And I should see "Total of marks: 2.00"
+    And I should see "Questions: 2"
+
+  @javascript
+  Scenario: Can delete the last question in a quiz.
+    Given the following "questions" exist:
+      | questioncategory | qtype     | name       | questiontext        |
+      | Test questions   | truefalse | Question A | This is question 01 |
+    And quiz "Quiz 1" contains the following questions:
+      | question   | page |
+      | Question A | 1    |
+    When I navigate to "Edit quiz" in current page administration
+    And I click on "Select multiple items" "button"
+    And I click on "selectquestion-1" "checkbox"
+    And I click on "Delete selected" "button"
+    And I click on "Yes" "button" in the "Confirm" "dialogue"
+    Then I should see "Questions: 0"
+
+  @javascript
+  Scenario: Delete all questions by checking select all.
+    Given the following "questions" exist:
+      | questioncategory | qtype     | name       | questiontext        |
+      | Test questions   | truefalse | Question A | This is question 01 |
+      | Test questions   | truefalse | Question B | This is question 02 |
+      | Test questions   | truefalse | Question C | This is question 03 |
+    And quiz "Quiz 1" contains the following questions:
+      | question   | page |
+      | Question A | 1    |
+      | Question B | 1    |
+      | Question C | 2    |
+    And I navigate to "Edit quiz" in current page administration
+
+  # Confirm the starting point.
+    Then I should see "Question A" on quiz page "1"
+    And I should see "Question B" on quiz page "1"
+    And I should see "Question C" on quiz page "2"
+    And I should see "Total of marks: 3.00"
+    And I should see "Questions: 3"
+    And I should see "This quiz is open"
+
+  # Delete all questions in page. Page contains multiple questions
+    When I click on "Select multiple items" "button"
+    Then I click on "Select all" "link"
+    And I click on "Delete selected" "button"
+    And I click on "Yes" "button" in the "Confirm" "dialogue"
+
+    Then I should not see "Question A" on quiz page "1"
+    And I should not see "Question B" on quiz page "1"
+    And I should not see "Question C" on quiz page "2"
+    And I should see "Total of marks: 0.00"
+    And I should see "Questions: 0"
+
+  @javascript
+  Scenario: Deselect all questions by checking deselect all.
+    Given the following "questions" exist:
+      | questioncategory | qtype     | name       | questiontext        |
+      | Test questions   | truefalse | Question A | This is question 01 |
+      | Test questions   | truefalse | Question B | This is question 02 |
+      | Test questions   | truefalse | Question C | This is question 03 |
+    And quiz "Quiz 1" contains the following questions:
+      | question   | page |
+      | Question A | 1    |
+      | Question B | 1    |
+      | Question C | 2    |
+    And I navigate to "Edit quiz" in current page administration
+
+  # Confirm the starting point.
+    Then I should see "Question A" on quiz page "1"
+    And I should see "Question B" on quiz page "1"
+    And I should see "Question C" on quiz page "2"
+
+  # Delete last question in last page. Page contains multiple questions
+    When I click on "Select multiple items" "button"
+    And I click on "Select all" "link"
+    Then the field "selectquestion-3" matches value "1"
+
+    When I click on "Deselect all" "link"
+    Then the field "selectquestion-3" matches value "0"
+
index 9909260..ad23f7a 100644 (file)
Binary files a/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-debug.js and b/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-debug.js differ
index 700c79f..9830b4a 100644 (file)
Binary files a/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-min.js and b/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-min.js differ
index 9909260..ad23f7a 100644 (file)
Binary files a/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate.js and b/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate.js differ
index dbfae9b..84ad28b 100644 (file)
Binary files a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js and b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js differ
index 58bd27c..958a11d 100644 (file)
Binary files a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js and b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js differ
index dbfae9b..84ad28b 100644 (file)
Binary files a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js and b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js differ
index 2e6e0bc..c7a75d8 100644 (file)
@@ -23,7 +23,6 @@
  */
 
 var CSS = {
-    REPAGINATECONTAINERCLASS: '.rpcontainerclass',
     REPAGINATECOMMAND: '#repaginatecommand'
 };
 
@@ -42,12 +41,12 @@ Y.extend(POPUP, Y.Base, {
     body: null,
 
     initializer: function() {
-        var rpcontainerclass = Y.one(CSS.REPAGINATECONTAINERCLASS);
+        var repaginatebutton = Y.one(CSS.REPAGINATECOMMAND);
 
         // Set popup header and body.
-        this.header = rpcontainerclass.getAttribute(PARAMS.HEADER);
-        this.body = rpcontainerclass.getAttribute(PARAMS.FORM);
-        Y.one(CSS.REPAGINATECOMMAND).on('click', this.display_dialog, this);
+        this.header = repaginatebutton.getData(PARAMS.HEADER);
+        this.body = repaginatebutton.getData(PARAMS.FORM);
+        repaginatebutton.on('click', this.display_dialog, this);
     },
 
     display_dialog: function(e) {
index d601b95..d889303 100644 (file)
@@ -64,6 +64,52 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
         M.mod_quiz.quizbase.register_module(this);
         Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
         Y.delegate('click', this.handle_data_action, BODY, SELECTOR.DEPENDENCY_LINK, this);
+        this.initialise_select_multiple();
+    },
+
+    /**
+     * Initialize the select multiple options
+     *
+     * Add actions to the buttons that enable multiple slots to be selected and managed at once.
+     *
+     * @method initialise_select_multiple
+     * @protected
+     */
+    initialise_select_multiple: function() {
+        // Click select multiple button to show the select all options.
+        Y.one(SELECTOR.SELECTMULTIPLEBUTTON).on('click', function(e) {
+            e.preventDefault();
+            Y.one('body').addClass(CSS.SELECTMULTIPLE);
+        });
+
+        // Click cancel button to show the select all options.
+        Y.one(SELECTOR.SELECTMULTIPLECANCELBUTTON).on('click', function(e) {
+            e.preventDefault();
+            Y.one('body').removeClass(CSS.SELECTMULTIPLE);
+        });
+
+        // Click select all link to check all the checkboxes.
+        Y.one(SELECTOR.SELECTALL).on('click', function(e) {
+            e.preventDefault();
+            Y.all(SELECTOR.SELECTMULTIPLECHECKBOX).set('checked', 'checked');
+        });
+
+        // Click deselect all link to show the select all checkboxes.
+        Y.one(SELECTOR.DESELECTALL).on('click', function(e) {
+            e.preventDefault();
+            Y.all(SELECTOR.SELECTMULTIPLECHECKBOX).set('checked', '');
+        });
+
+        // Disable delete multiple button by default.
+        Y.one(SELECTOR.SELECTMULTIPLEDELETEBUTTON).setAttribute('disabled', 'disabled');
+
+        // Assign the delete method to the delete multiple button.
+        Y.delegate('click', this.delete_multiple_with_confirmation, BODY, SELECTOR.SELECTMULTIPLEDELETEBUTTON, this);
+
+        // Enable the delete all button only when at least one slot is selected.
+        Y.delegate('click', this.toggle_select_all_buttons_enabled, BODY, SELECTOR.SELECTMULTIPLECHECKBOX, this);
+        Y.delegate('click', this.toggle_select_all_buttons_enabled, BODY, SELECTOR.SELECTALL, this);
+        Y.delegate('click', this.toggle_select_all_buttons_enabled, BODY, SELECTOR.DESELECTALL, this);
     },
 
     /**
@@ -136,6 +182,22 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
         return null;
     },
 
+    /**
+     * If a select multiple checkbox is checked enable the buttons in the select multiple
+     * toolbar otherwise disable it.
+     *
+     * @method toggle_select_all_buttons_enabled
+     */
+    toggle_select_all_buttons_enabled: function() {
+        var checked = Y.all(SELECTOR.SELECTMULTIPLECHECKBOX + ':checked');
+        var deletebutton = Y.one(SELECTOR.SELECTMULTIPLEDELETEBUTTON);
+        if (checked && !checked.isEmpty()) {
+            deletebutton.removeAttribute('disabled');
+        } else {
+            deletebutton.setAttribute('disabled', 'disabled');
+        }
+    },
+
     /**
      * Deletes the given activity or resource after confirmation.
      *
@@ -166,7 +228,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
 
         // If it is confirmed.
         confirm.on('complete-yes', function() {
-
             var spinner = this.add_spinner(element);
             var data = {
                 'class': 'resource',
@@ -185,10 +246,66 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
             });
 
         }, this);
-
-        return this;
     },
 
+    /**
+     * Deletes the given activities or resources after confirmation.
+     *
+     * @protected
+     * @method delete_multiple_with_confirmation
+     * @param {EventFacade} ev The event that was fired.
+     * @chainable
+     */
+    delete_multiple_with_confirmation: function(ev) {
+        ev.preventDefault();
+
+        var ids = '';
+        var slots = [];
+        Y.all(SELECTOR.SELECTMULTIPLECHECKBOX + ':checked').each(function(node) {
+            var slot = Y.Moodle.mod_quiz.util.slot.getSlotFromComponent(node);
+            ids += ids === '' ? '' : ',';
+            ids += Y.Moodle.mod_quiz.util.slot.getId(slot);
+            slots.push(slot);
+        });
+        var element = Y.one('div.mod-quiz-edit-content');
+
+        // Do nothing if no slots are selected.
+        if (!slots || !slots.length) {
+            return;
+        }
+
+        // Create the confirmation dialogue.
+        var confirm = new M.core.confirm({
+            question: M.util.get_string('areyousureremoveselected', 'quiz'),
+            modal: true
+        });
+
+        // If it is confirmed.
+        confirm.on('complete-yes', function() {
+            var spinner = this.add_spinner(element);
+            var data = {
+                'class': 'resource',
+                field: 'deletemultiple',
+                ids: ids
+            };
+            // Delete items on server.
+            this.send_request(data, spinner, function(response) {
+                // Delete locally if deleted on server.
+                if (response.deleted) {
+                    // Actually remove the element.
+                    Y.all(SELECTOR.SELECTMULTIPLECHECKBOX + ':checked').each(function(node) {
+                        Y.Moodle.mod_quiz.util.slot.remove(node.ancestor('li.activity'));
+                    });
+                    // Update the page numbers and sections.
+                    this.reorganise_edit_page();
+
+                    // Remove the select multiple options.
+                    Y.one('body').removeClass(CSS.SELECTMULTIPLE);
+                }
+            });
+
+        }, this);
+    },
 
     /**
      * Edit the maxmark for the resource
@@ -446,6 +563,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
             'value': 0
         }
     }
+
 });
 
 M.mod_quiz.resource_toolbox = null;
index 22f6268..f2201cc 100644 (file)
@@ -26,6 +26,7 @@ var CSS = {
         PAGE: 'page',
         SECTIONHIDDENCLASS: 'hidden',
         SECTIONIDPREFIX: 'section-',
+        SELECTMULTIPLE: 'select-multiple',
         SLOT: 'slot',
         SHOW: 'editing_show',
         TITLEEDITOR: 'titleeditor'
@@ -43,7 +44,8 @@ var CSS = {
         COMMANDSPAN: '.commands',
         CONTENTAFTERLINK: 'div.contentafterlink',
         CONTENTWITHOUTLINK: 'div.contentwithoutlink',
-        DELETESECTIONICON: 'a.editing_delete .icon',
+        DELETESECTIONICON: 'a.editing_delete img',
+        DESELECTALL: '#questiondeselectall',
         EDITMAXMARK: 'a.editing_maxmark',
         EDITSECTION: 'a.editing_section',
         EDITSECTIONICON: 'a.editing_section .icon',
@@ -63,6 +65,11 @@ var CSS = {
         SECTIONUL: 'ul.section',
         SECTIONFORM: '.instancesectioncontainer form',
         SECTIONINPUT: 'input[name=section]',
+        SELECTMULTIPLEBUTTON: '#selectmultiplecommand',
+        SELECTMULTIPLECANCELBUTTON: '#selectmultiplecancelcommand',
+        SELECTMULTIPLECHECKBOX: '.select-multiple-checkbox',
+        SELECTMULTIPLEDELETEBUTTON: '#selectmultipledeletecommand',
+        SELECTALL: '#questionselectall',
         SHOW: 'a.' + CSS.SHOW,
         SLOTLI: 'li.slot',
         SUMMARKS: '.mod_quiz_summarks'
@@ -101,6 +108,7 @@ Y.extend(TOOLBOX, Y.Base, {
         if (!data) {
             data = {};
         }
+
         // Handle any variables which we must pass back through to
         var pageparams = this.get('config').pageparams,
             varname;