MDL-40990 quiz: option to require prev Q finished before next shown

Further improvements to this code, including resolving edge cases:

* The new feature can only be used when it is possible for the
previous question in the quiz to be complete.

* Also, this new feature cannot be used in combination with shuffle
questions, because that make no sense; nor in combination with
sequential navigation, because to make that work properly would be a lot
of effort. If someone needs that to work later, it should be possible
for them to implement it.

* There were changes in the edit renderer API, to try to make things
more  consistent, and to make it less likely we will need to change
things again in the future. See mod/quiz/upgrade.txt.

* As part of this change, the styling of the Edit quiz page was tweaked
to make slighly more efficient use of the horizontal space, and to be
more symmetrical.
This commit is contained in:
Tim Hunt
2015-03-17 17:16:22 +00:00
parent f7785e42b3
commit 441d284ada
29 changed files with 1248 additions and 854 deletions
+79 -31
View File
@@ -446,13 +446,18 @@ class quiz_attempt {
/** @var int maximum number of slots in the quiz for the review page to default to show all. */
const MAX_SLOTS_FOR_DEFAULT_REVIEW_SHOW_ALL = 50;
// Basic data.
/** @var quiz object containing the quiz settings. */
protected $quizobj;
/** @var stdClass the quiz_attempts row. */
protected $attempt;
/** @var question_usage_by_activity the question usage for this quiz attempt. */
protected $quba;
/** @var array of quiz_slots rows. */
protected $slots;
/** @var array page no => array of slot numbers on the page in order. */
protected $pagelayout;
@@ -477,6 +482,8 @@ class quiz_attempt {
* of the state of each question. Else just set up the basic details of the attempt.
*/
public function __construct($attempt, $quiz, $cm, $course, $loadquestions = true) {
global $DB;
$this->attempt = $attempt;
$this->quizobj = new quiz($quiz, $cm, $course);
@@ -485,6 +492,10 @@ class quiz_attempt {
}
$this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid);
$this->slots = $DB->get_records('quiz_slots',
array('quizid' => $this->get_quizid()), 'slot',
'slot, page, requireprevious, questionid, maxmark');
$this->determine_layout();
$this->number_questions();
}
@@ -987,6 +998,21 @@ class quiz_attempt {
}
/**
* Checks whether the question in this slot requires the previous question to have been completed.
*
* @param int $slot the number used to identify this question within this attempt.
* @return bool whether the previous question must have been completed before this one can be seen.
*/
public function is_blocked_by_previous_question($slot) {
return $slot > 1 && $this->slots[$slot]->requireprevious &&
!$this->get_quiz()->shufflequestions &&
$this->get_navigation_method() != QUIZ_NAVMETHOD_SEQ &&
!$this->quba->get_question_state($slot - 1)->is_finished() &&
$this->quba->can_question_finish_during_attempt($slot - 1);
}
/**
* Get the displayed question number for a slot.
* @param int $slot the number used to identify this question within this attempt.
* @return string the displayed question number for the question in this slot.
* For example '1', '2', '3' or 'i'.
@@ -1168,36 +1194,6 @@ class quiz_attempt {
return new moodle_url('/mod/quiz/processattempt.php');
}
/**
* Return slot object for the given slotnumber in a given quizid
*
* @param int $quizid
* @param int $slotnumber
*/
public function get_slot_object($quizid, $slotnumber) {
global $DB;
return $DB->get_record('quiz_slots', array('slot' => $slotnumber, 'quizid' => $quizid));
}
/**
* Checks whether it requires previous question. If the previous question is not completed
* return a message in descripyiom question type format, otherwise returns null
*
* @param int $slot
*/
public function require_previous_question($slot) {
$quiz = $this->get_quiz();
$currentslot = $this->get_slot_object($quiz->id, $slot);
$previousslot = $this->get_slot_object($quiz->id, $currentslot->slot - 1);
if ($currentslot->requireprevious && $previousslot) {
if ($this->get_question_status($previousslot->slot, false) == 'Not yet answered') {
return $this->quba->replace_question_with_a_description_qtye($currentslot);
}
}
return null;
}
/**
* @param int $slot indicates which question to link to.
* @param int $page if specified, the URL of this particular page of the attempt, otherwise
@@ -1285,11 +1281,63 @@ class quiz_attempt {
* @return string HTML for the question in its current state.
*/
public function render_question($slot, $reviewing, $thispageurl = null) {
if ($this->is_blocked_by_previous_question($slot)) {
$placeholderqa = $this->make_blocked_question_placeholder($slot);
$displayoptions = $this->get_display_options($reviewing);
$displayoptions->manualcomment = question_display_options::HIDDEN;
$displayoptions->history = question_display_options::HIDDEN;
$displayoptions->readonly = true;
return html_writer::div($placeholderqa->render($displayoptions,
$this->get_question_number($slot)),
'mod_quiz-blocked_question_warning');
}
return $this->quba->render_question($slot,
$this->get_display_options_with_edit_link($reviewing, $slot, $thispageurl),
$this->get_question_number($slot));
}
/**
* Create a fake question to be displayed in place of a question that is blocked
* until the previous question has been answered.
*
* @param unknown $slot int slot number of the question to replace.
* @return question_definition the placeholde question.
*/
protected function make_blocked_question_placeholder($slot) {
$replacedquestion = $this->get_question_attempt($slot)->get_question();
question_bank::load_question_definition_classes('description');
$question = new qtype_description_question();
$question->id = $replacedquestion->id;
$question->category = null;
$question->parent = 0;
$question->qtype = question_bank::get_qtype('description');
$question->name = '';
$question->questiontext = get_string('questiondependsonprevious', 'quiz');
$question->questiontextformat = FORMAT_HTML;
$question->generalfeedback = '';
$question->defaultmark = $this->quba->get_question_max_mark($slot);
$question->length = $replacedquestion->length;
$question->penalty = 0;
$question->stamp = '';
$question->version = 0;
$question->hidden = 0;
$question->timecreated = null;
$question->timemodified = null;
$question->createdby = null;
$question->modifiedby = null;
$placeholderqa = new question_attempt($question, $this->quba->get_id(),
null, $this->quba->get_question_max_mark($slot));
$placeholderqa->set_slot($slot);
$placeholderqa->start($this->get_quiz()->preferredbehaviour, 1);
$placeholderqa->set_flagged($this->is_question_flagged($slot));
return $placeholderqa;
}
/**
* Like {@link render_question()} but displays the question at the past step
* indicated by $seq, rather than showing the latest step.
@@ -57,7 +57,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
$qinstances = new backup_nested_element('question_instances');
$qinstance = new backup_nested_element('question_instance', array('id'), array(
'slot', 'page', 'questionid', 'maxmark'));
'slot', 'page', 'requireprevious', 'questionid', 'maxmark'));
$feedbacks = new backup_nested_element('feedbacks');
+116 -128
View File
@@ -59,7 +59,7 @@ class edit_renderer extends \plugin_renderer_base {
// Information at the top.
$output .= $this->quiz_state_warnings($structure);
$output .= $this->quiz_information($structure);
$output .= $this->maximum_grade_input($quizobj->get_quiz(), $this->page->url);
$output .= $this->maximum_grade_input($structure, $pageurl);
$output .= $this->repaginate_button($structure, $pageurl);
$output .= $this->total_marks($quizobj->get_quiz());
@@ -83,8 +83,7 @@ class edit_renderer extends \plugin_renderer_base {
$output .= $this->end_section_list();
// Inialise the JavaScript.
$this->initialise_editing_javascript($quizobj->get_course(), $quizobj->get_quiz(),
$structure, $contexts, $pagevars, $pageurl);
$this->initialise_editing_javascript($structure, $contexts, $pagevars, $pageurl);
// Include the contents of any other popups required.
if ($structure->can_be_edited()) {
@@ -152,11 +151,11 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Render the form for setting a quiz' overall grade
*
* @param \stdClass $quiz the quiz settings from the database.
* @param structure $structure the quiz structure.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
public function maximum_grade_input($quiz, \moodle_url $pageurl) {
public function maximum_grade_input($structure, \moodle_url $pageurl) {
$output = '';
$output .= html_writer::start_div('maxgrade');
$output .= html_writer::start_tag('form', array('method' => 'post', 'action' => 'edit.php',
@@ -165,8 +164,8 @@ class edit_renderer extends \plugin_renderer_base {
$output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
$output .= html_writer::input_hidden_params($pageurl);
$a = html_writer::empty_tag('input', array('type' => 'text', 'id' => 'inputmaxgrade',
'name' => 'maxgrade', 'size' => ($quiz->decimalpoints + 2),
'value' => quiz_format_grade($quiz, $quiz->grade)));
'name' => 'maxgrade', 'size' => ($structure->get_decimal_places_for_grades() + 2),
'value' => $structure->formatted_quiz_grade()));
$output .= html_writer::tag('label', get_string('maximumgradex', '', $a),
array('for' => 'inputmaxgrade'));
$output .= html_writer::empty_tag('input', array('type' => 'submit',
@@ -342,10 +341,8 @@ class edit_renderer extends \plugin_renderer_base {
$contexts, $pagevars, $pageurl) {
$output = '';
$previousquestion = null;
foreach ($structure->get_questions_in_section($section->id) as $question) {
$output .= $this->question_row($structure, $question, $previousquestion, $contexts, $pagevars, $pageurl);
$previousquestion = $question;
foreach ($structure->get_slots_in_section($section->id) as $slot) {
$output .= $this->question_row($structure, $slot, $contexts, $pagevars, $pageurl);
}
return html_writer::tag('ul', $output, array('class' => 'section img-text'));
}
@@ -354,31 +351,31 @@ class edit_renderer extends \plugin_renderer_base {
* Displays one question with the surrounding controls.
*
* @param structure $structure object containing the structure of the quiz.
* @param \stdClass $question data from the question and quiz_slots tables.
* @param \stdClass $previousquestion data from the question and quiz_slots tables.
* @param int $slot which slot we are outputting.
* @param \question_edit_contexts $contexts the relevant question bank contexts.
* @param array $pagevars the variables from {@link \question_edit_setup()}.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
public function question_row(structure $structure, $question, $previousquestion, $contexts, $pagevars, $pageurl) {
public function question_row(structure $structure, $slot, $contexts, $pagevars, $pageurl) {
$output = '';
$output .= $this->page_row($structure, $question, $contexts, $pagevars, $pageurl);
$output .= $this->page_row($structure, $slot, $contexts, $pagevars, $pageurl);
// Page split/join icon.
$joinhtml = '';
if ($structure->can_be_edited() && !$structure->is_last_slot_in_quiz($question->slot)) {
$joinhtml = $this->page_split_join_button($structure->get_quiz(),
$question, !$structure->is_last_slot_on_page($question->slot));
if ($structure->can_be_edited() && !$structure->is_last_slot_in_quiz($slot)) {
$joinhtml = $this->page_split_join_button($structure, $slot);
}
// Question HTML.
$questionhtml = $this->question($structure, $question, $previousquestion, $pageurl);
$questionclasses = 'activity ' . $question->qtype . ' qtype_' . $question->qtype . ' slot';
$questionhtml = $this->question($structure, $slot, $pageurl);
$qtype = $structure->get_question_type_for_slot($slot);
$questionclasses = 'activity ' . $qtype . ' qtype_' . $qtype . ' slot';
$output .= html_writer::tag('li', $questionhtml . $joinhtml,
array('class' => $questionclasses, 'id' => 'slot-' . $question->slotid));
array('class' => $questionclasses, 'id' => 'slot-' . $structure->get_slot_id_for_slot($slot),
'data-canfinish' => $structure->can_finish_during_the_attempt($slot)));
return $output;
}
@@ -387,30 +384,32 @@ class edit_renderer extends \plugin_renderer_base {
* Displays one question with the surrounding controls.
*
* @param structure $structure object containing the structure of the quiz.
* @param \stdClass $question data from the question and quiz_slots tables.
* @param int $slot the first slot on the page we are outputting.
* @param \question_edit_contexts $contexts the relevant question bank contexts.
* @param array $pagevars the variables from {@link \question_edit_setup()}.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
public function page_row(structure $structure, $question, $contexts, $pagevars, $pageurl) {
public function page_row(structure $structure, $slot, $contexts, $pagevars, $pageurl) {
$output = '';
$pagenumber = $structure->get_page_number_for_slot($slot);
// Put page in a span for easier styling.
$page = html_writer::tag('span', get_string('page') . ' ' . $question->page,
$page = html_writer::tag('span', get_string('page') . ' ' . $pagenumber,
array('class' => 'text'));
if ($structure->is_first_slot_on_page($question->slot)) {
if ($structure->is_first_slot_on_page($slot)) {
// Add the add-menu at the page level.
$addmenu = html_writer::tag('span', $this->add_menu_actions($structure,
$question->page, $pageurl, $contexts, $pagevars),
$pagenumber, $pageurl, $contexts, $pagevars),
array('class' => 'add-menu-outer'));
$addquestionform = $this->add_question_form($structure,
$question->page, $pageurl, $pagevars);
$pagenumber, $pageurl, $pagevars);
$output .= html_writer::tag('li', $page . $addmenu . $addquestionform,
array('class' => 'pagenumber activity yui3-dd-drop page', 'id' => 'page-' . $question->page));
array('class' => 'pagenumber activity yui3-dd-drop page', 'id' => 'page-' . $pagenumber));
}
return $output;
@@ -544,30 +543,29 @@ class edit_renderer extends \plugin_renderer_base {
* Display a question.
*
* @param structure $structure object containing the structure of the quiz.
* @param \stdClass $question data from the question and quiz_slots tables.
* @param \stdClass $previousquestion data from the question and quiz_slots tables.
* @param int $slot the first slot on the page we are outputting.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
public function question(structure $structure, $question, $previousquestion, \moodle_url $pageurl) {
public function question(structure $structure, $slot, \moodle_url $pageurl) {
$output = '';
$output .= html_writer::start_tag('div');
if ($structure->can_be_edited()) {
$output .= $this->question_move_icon($question);
$output .= $this->question_move_icon($structure, $slot);
}
$output .= html_writer::start_div('mod-indent-outer');
$output .= $this->question_number($question->displayednumber);
$output .= $this->question_number($structure->get_displayed_number_for_slot($slot));
// This div is used to indent the content.
$output .= html_writer::div('', 'mod-indent');
// Display the link to the question (or do nothing if question has no url).
if ($question->qtype == 'random') {
$questionname = $this->random_question($structure, $question, $pageurl);
if ($structure->get_question_type_for_slot($slot) == 'random') {
$questionname = $this->random_question($structure, $slot, $pageurl);
} else {
$questionname = $this->question_name($structure, $question, $pageurl);
$questionname = $this->question_name($structure, $slot, $pageurl);
}
// Start the div for the activity title, excluding the edit icons.
@@ -579,13 +577,15 @@ class edit_renderer extends \plugin_renderer_base {
// Action icons.
$questionicons = '';
$questionicons .= $this->question_preview_icon($structure->get_quiz(), $question);
$questionicons .= $this->question_preview_icon($structure->get_quiz(), $structure->get_question_in_slot($slot));
if ($structure->can_be_edited()) {
$questionicons .= $this->question_remove_icon($question, $pageurl);
$questionicons .= $this->question_dependency_icon($structure->get_quiz(), $question, $previousquestion);
$questionicons .= $this->question_remove_icon($structure, $slot, $pageurl);
}
$questionicons .= $this->marked_out_of_field($structure->get_quiz(), $question);
$questionicons .= $this->marked_out_of_field($structure, $slot);
$output .= html_writer::span($questionicons, 'actions'); // Required to add js spinner icon.
if ($structure->can_be_edited()) {
$output .= $this->question_dependency_icon($structure, $slot);
}
// End of indentation div.
$output .= html_writer::end_tag('div');
@@ -597,10 +597,11 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Render the move icon.
*
* @param \stdClass $question data from the question and quiz_slots tables.
* @return string The markup for the move action, or an empty string if not available.
* @param structure $structure object containing the structure of the quiz.
* @param int $slot the first slot on the page we are outputting.
* @return string The markup for the move action.
*/
public function question_move_icon($question) {
public function question_move_icon(structure $structure, $slot) {
return html_writer::link(new \moodle_url('#'),
$this->pix_icon('i/dragdrop', get_string('move'), 'moodle', array('class' => 'iconsmall', 'title' => '')),
array('class' => 'editing_move', 'data-action' => 'move')
@@ -650,12 +651,13 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Render an icon to remove a question from the quiz.
*
* @param object $question The module to produce a move button for.
* @param structure $structure object containing the structure of the quiz.
* @param int $slot the first slot on the page we are outputting.
* @param \moodle_url $pageurl the canonical URL of the edit page.
* @return string HTML to output.
*/
public function question_remove_icon($question, $pageurl) {
$url = new \moodle_url($pageurl, array('sesskey' => sesskey(), 'remove' => $question->slot));
public function question_remove_icon(structure $structure, $slot, $pageurl) {
$url = new \moodle_url($pageurl, array('sesskey' => sesskey(), 'remove' => $slot));
$strdelete = get_string('delete');
$image = $this->pix_icon('t/delete', $strdelete);
@@ -667,15 +669,14 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Display an icon to split or join two pages of the quiz.
*
* @param \stdClass $quiz the quiz settings from the database.
* @param \stdClass $question data from the question and quiz_slots tables.
* @param bool $insertpagebreak if true, show an insert page break icon.
* else show a join pages icon.
* @param structure $structure object containing the structure of the quiz.
* @param int $slot the first slot on the page we are outputting.
* @return string HTML to output.
*/
public function page_split_join_button($quiz, $question, $insertpagebreak) {
$url = new \moodle_url('repaginate.php', array('cmid' => $quiz->cmid, 'quizid' => $quiz->id,
'slot' => $question->slot, 'repag' => $insertpagebreak ? 2 : 1, 'sesskey' => sesskey()));
public function page_split_join_button($structure, $slot) {
$insertpagebreak = !$structure->is_last_slot_on_page($slot);
$url = new \moodle_url('repaginate.php', array('quizid' => $structure->get_quizid(),
'slot' => $slot, 'repag' => $insertpagebreak ? 2 : 1, 'sesskey' => sesskey()));
if ($insertpagebreak) {
$title = get_string('addpagebreak', 'quiz');
@@ -689,8 +690,8 @@ class edit_renderer extends \plugin_renderer_base {
// Disable the link if quiz has attempts.
$disabled = null;
if (quiz_has_attempts($quiz->id)) {
$disabled = "disabled";
if (!$structure->can_be_edited()) {
$disabled = 'disabled';
}
return html_writer::span($this->action_link($url, $image, null, array('title' => $title,
'class' => 'page_split_join cm-edit-action', 'disabled' => $disabled, 'data-action' => $action)),
@@ -698,55 +699,42 @@ class edit_renderer extends \plugin_renderer_base {
}
/**
* Display an icon Add/Remove dependency
* Display the icon for whether this question can only be seen if the previous
* one has been answered.
*
* @param object $quiz
* @param object $question
* @param object $previousquestion
* @param bool $dependencysetting
* @param structure $structure object containing the structure of the quiz.
* @param int $slot the first slot on the page we are outputting.
* @return string HTML to output.
*/
public function question_dependency_icon($quiz, $question, $previousquestion, $dependencysetting = true) {
if (!$dependencysetting) {
return null;
}
// Check whether the current question qualifies for dependency.
// What about random questions?
$unqualifiedqtypes = array('description', 'essay');
// Current question is not the first question in the quiz.
if ($question->slot == 1) {
return ' ';
}
// Current question is not a description or an essay question type.
if (in_array($question->qtype, $unqualifiedqtypes)) {
return ' ';
}
// Previous question is not a description or an essay question type.
if (in_array($previousquestion->qtype, $unqualifiedqtypes)) {
return ' ';
}
// Process qualified questions.
$url = new \moodle_url('questiondependency.php', array('cmid' => $quiz->cmid, 'quizid' => $quiz->id,
'slotid' => $question->slotid, 'sesskey' => sesskey()));
if ($question->requireprevious) {
$title = get_string('removequestiondependency', 'quiz');
$image = $this->pix_icon('e/remove_page_break', $title);
$action = 'linkpage';
public function question_dependency_icon($structure, $slot) {
$a = array(
'thisq' => $structure->get_displayed_number_for_slot($slot),
'previousq' => $structure->get_displayed_number_for_slot(max($slot - 1, 1)),
);
if ($structure->is_question_dependent_on_previous_slot($slot)) {
$title = get_string('questiondependencyremove', 'quiz', $a);
$image = $this->pix_icon('t/locked', get_string('questiondependsonprevious', 'quiz'),
'moodle', array('title' => ''));
$action = 'removedependency';
} else {
$title = get_string('addquestiondependency', 'quiz');
$image = $this->pix_icon('e/insert_page_break', $title);
$action = 'unlinkpage';
$title = get_string('questiondependencyadd', 'quiz', $a);
$image = $this->pix_icon('t/unlocked', get_string('questiondependencyfree', 'quiz'),
'moodle', array('title' => ''));
$action = 'adddependency';
}
// Disable the link if quiz has attempts.
$disabled = null;
if (quiz_has_attempts($quiz->id)) {
$disabled = "disabled";
if (!$structure->can_be_edited()) {
$disabled = 'disabled';
}
return html_writer::span($this->action_link($url, $image, null, array('title' => $title,
'class' => 'question_dependency_icon', 'disabled' => $disabled, 'data-action' => $action)),
'page_split_join_wrapper');
$extraclass = '';
if (!$structure->can_question_depend_on_previous_slot($slot)) {
$extraclass = ' question_dependency_cannot_depend';
}
return html_writer::span($this->action_link('#', $image, null, array('title' => $title,
'class' => 'cm-edit-action', 'disabled' => $disabled, 'data-action' => $action)),
'question_dependency_wrapper' . $extraclass);
}
/**
@@ -756,13 +744,14 @@ class edit_renderer extends \plugin_renderer_base {
* without a link
*
* @param structure $structure object containing the structure of the quiz.
* @param \stdClass $question data from the question and quiz_slots tables.
* @param int $slot which slot we are outputting.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
public function question_name(structure $structure, $question, $pageurl) {
public function question_name(structure $structure, $slot, $pageurl) {
$output = '';
$question = $structure->get_question_in_slot($slot);
$editurl = new \moodle_url('/question/question.php', array(
'returnurl' => $pageurl->out_as_local_url(),
'cmid' => $structure->get_cmid(), 'id' => $question->id));
@@ -793,12 +782,13 @@ class edit_renderer extends \plugin_renderer_base {
* and also to see that category in the question bank.
*
* @param structure $structure object containing the structure of the quiz.
* @param \stdClass $question data from the question and quiz_slots tables.
* @param int $slot which slot we are outputting.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return string HTML to output.
*/
public function random_question(structure $structure, $question, $pageurl) {
public function random_question(structure $structure, $slot, $pageurl) {
$question = $structure->get_question_in_slot($slot);
$editurl = new \moodle_url('/question/question.php', array(
'returnurl' => $pageurl->out_as_local_url(),
'cmid' => $structure->get_cmid(), 'id' => $question->id));
@@ -831,14 +821,14 @@ class edit_renderer extends \plugin_renderer_base {
/**
* Display the 'marked out of' information for a question.
* Along with the regrade action.
* @param \stdClass $quiz the quiz settings from the database.
* @param \stdClass $question data from the question and quiz_slots tables.
* @param structure $structure object containing the structure of the quiz.
* @param int $slot which slot we are outputting.
* @return string HTML to output.
*/
public function marked_out_of_field($quiz, $question) {
if ($question->length == 0) {
public function marked_out_of_field(structure $structure, $slot) {
if (!$structure->is_real_question($slot)) {
$output = html_writer::span('',
'instancemaxmark decimalplaces_' . quiz_get_grade_format($quiz));
'instancemaxmark decimalplaces_' . $structure->get_decimal_places_for_question_marks());
$output .= html_writer::span(
$this->pix_icon('spacer', '', 'moodle', array('class' => 'editicon visibleifjs', 'title' => '')),
@@ -846,8 +836,8 @@ class edit_renderer extends \plugin_renderer_base {
return html_writer::span($output, 'instancemaxmarkcontainer infoitem');
}
$output = html_writer::span(quiz_format_question_grade($quiz, $question->maxmark),
'instancemaxmark decimalplaces_' . quiz_get_grade_format($quiz),
$output = html_writer::span($structure->formatted_question_grade($slot),
'instancemaxmark decimalplaces_' . $structure->get_decimal_places_for_question_marks(),
array('title' => get_string('maxmark', 'quiz')));
$output .= html_writer::span(
@@ -912,30 +902,28 @@ class edit_renderer extends \plugin_renderer_base {
* Initialise the JavaScript for the general editing. (JavaScript for popups
* is handled with the specific code for those.)
*
* @param \stdClass $course the course settings from the database.
* @param \stdClass $quiz the quiz settings from the database.
* @param structure $structure object containing the structure of the quiz.
* @param \question_edit_contexts $contexts the relevant question bank contexts.
* @param array $pagevars the variables from {@link \question_edit_setup()}.
* @param \moodle_url $pageurl the canonical URL of this page.
* @return bool Always returns true
*/
protected function initialise_editing_javascript($course, $quiz, structure $structure,
protected function initialise_editing_javascript(structure $structure,
\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
$config = new \stdClass();
$config->resourceurl = '/mod/quiz/edit_rest.php';
$config->sectionurl = '/mod/quiz/edit_rest.php';
$config->pageparams = array();
$config->questiondecimalpoints = $quiz->questiondecimalpoints;
$config->questiondecimalpoints = $structure->get_decimal_places_for_question_marks();
$config->pagehtml = $this->new_page_template($structure, $contexts, $pagevars, $pageurl);
$config->addpageiconhtml = $this->add_page_icon_template($structure, $quiz);
$config->addpageiconhtml = $this->add_page_icon_template($structure);
$this->page->requires->yui_module('moodle-mod_quiz-toolboxes',
'M.mod_quiz.init_resource_toolbox',
array(array(
'courseid' => $course->id,
'quizid' => $quiz->id,
'courseid' => $structure->get_courseid(),
'quizid' => $structure->get_quizid(),
'ajaxurl' => $config->resourceurl,
'config' => $config,
))
@@ -946,9 +934,8 @@ class edit_renderer extends \plugin_renderer_base {
$this->page->requires->yui_module('moodle-mod_quiz-toolboxes',
'M.mod_quiz.init_section_toolbox',
array(array(
'courseid' => $course->id,
'quizid' => $quiz->id,
'format' => $course->format,
'courseid' => $structure,
'quizid' => $structure->get_quizid(),
'ajaxurl' => $config->sectionurl,
'config' => $config,
))
@@ -956,16 +943,16 @@ class edit_renderer extends \plugin_renderer_base {
$this->page->requires->yui_module('moodle-mod_quiz-dragdrop', 'M.mod_quiz.init_section_dragdrop',
array(array(
'courseid' => $course->id,
'quizid' => $quiz->id,
'courseid' => $structure,
'quizid' => $structure->get_quizid(),
'ajaxurl' => $config->sectionurl,
'config' => $config,
)), null, true);
$this->page->requires->yui_module('moodle-mod_quiz-dragdrop', 'M.mod_quiz.init_resource_dragdrop',
array(array(
'courseid' => $course->id,
'quizid' => $quiz->id,
'courseid' => $structure,
'quizid' => $structure->get_quizid(),
'ajaxurl' => $config->resourceurl,
'config' => $config,
)), null, true);
@@ -999,6 +986,10 @@ class edit_renderer extends \plugin_renderer_base {
'dragtostart',
'numquestionsx',
'removepagebreak',
'questiondependencyadd',
'questiondependencyfree',
'questiondependencyremove',
'questiondependsonprevious',
), 'quiz');
foreach (\question_bank::get_all_qtypes() as $qtype => $notused) {
@@ -1023,11 +1014,10 @@ class edit_renderer extends \plugin_renderer_base {
return '';
}
$question = $structure->get_question_in_slot(1);
$pagehtml = $this->page_row($structure, $question, $contexts, $pagevars, $pageurl);
$pagehtml = $this->page_row($structure, 1, $contexts, $pagevars, $pageurl);
// Normalise the page number.
$pagenumber = $question->page;
$pagenumber = $structure->get_page_number_for_slot(1);
$strcontexts = array();
$strcontexts[] = 'page-';
$strcontexts[] = get_string('page') . ' ';
@@ -1049,17 +1039,15 @@ class edit_renderer extends \plugin_renderer_base {
* HTML for a page, with ids stripped, so it can be used as a javascript template.
*
* @param structure $structure object containing the structure of the quiz.
* @param \stdClass $quiz the quiz settings.
* @return string HTML for a new icon
*/
protected function add_page_icon_template(structure $structure, $quiz) {
protected function add_page_icon_template(structure $structure) {
if (!$structure->has_questions()) {
return '';
}
$question = $structure->get_question_in_slot(1);
$html = $this->page_split_join_button($quiz, $question, true);
$html = $this->page_split_join_button($structure, 1);
return str_replace('&slot=1&', '&slot=%%SLOT%%&', $html);
}
+153 -23
View File
@@ -118,6 +118,112 @@ class structure {
return $this->questions[$this->slotsinorder[$slotnumber]->questionid];
}
/**
* Get the displayed question number (or 'i') for a given slot.
* @param int $slotnumber the index of the slot in question.
* @return string the question number ot display for this slot.
*/
public function get_displayed_number_for_slot($slotnumber) {
return $this->slotsinorder[$slotnumber]->displayednumber;
}
/**
* Get the page a given slot is on.
* @param int $slotnumber the index of the slot in question.
* @return int the page number of the page that slot is on.
*/
public function get_page_number_for_slot($slotnumber) {
return $this->slotsinorder[$slotnumber]->page;
}
/**
* Get the slot id of a given slot slot.
* @param int $slotnumber the index of the slot in question.
* @return int the page number of the page that slot is on.
*/
public function get_slot_id_for_slot($slotnumber) {
return $this->slotsinorder[$slotnumber]->id;
}
/**
* Get the question type in a given slot.
* @param int $slotnumber the index of the slot in question.
* @return string the question type (e.g. multichoice).
*/
public function get_question_type_for_slot($slotnumber) {
return $this->questions[$this->slotsinorder[$slotnumber]->questionid]->qtype;
}
/**
* Whether it would be possible, given the question types, etc. for the
* question in the given slot to require that the previous question had been
* answered before this one is displayed.
* @param int $slotnumber the index of the slot in question.
* @return bool can this question require the previous one.
*/
public function can_question_depend_on_previous_slot($slotnumber) {
return $slotnumber > 1 && $this->can_finish_during_the_attempt($slotnumber - 1);
}
/**
* Whether it is possible for another question to depend on this one finishing.
* Note that the answer is not exact, because of random questions, and sometimes
* questions cannot be depended upon because of quiz options.
* @param int $slotnumber the index of the slot in question.
* @return bool can this question finish naturally during the attempt?
*/
public function can_finish_during_the_attempt($slotnumber) {
if ($this->quizobj->get_quiz()->shufflequestions ||
$this->quizobj->get_navigation_method() == QUIZ_NAVMETHOD_SEQ) {
return false;
}
if ($this->get_question_type_for_slot($slotnumber) == 'random') {
return true;
}
if (isset($this->slotsinorder[$slotnumber]->canfinish)) {
return $this->slotsinorder[$slotnumber]->canfinish;
}
$quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $this->quizobj->get_context());
$tempslot = $quba->add_question(\question_bank::load_question(
$this->slotsinorder[$slotnumber]->questionid));
$quba->set_preferred_behaviour($this->quizobj->get_quiz()->preferredbehaviour);
$quba->start_all_questions();
$this->slotsinorder[$slotnumber]->canfinish = $quba->can_question_finish_during_attempt($tempslot);
return $this->slotsinorder[$slotnumber]->canfinish;
}
/**
* Whether it would be possible, given the question types, etc. for the
* question in the given slot to require that the previous question had been
* answered before this one is displayed.
* @param int $slotnumber the index of the slot in question.
* @return bool can this question require the previous one.
*/
public function is_question_dependent_on_previous_slot($slotnumber) {
return $this->slotsinorder[$slotnumber]->requireprevious;
}
/**
* Is a particular question in this attempt a real question, or something like a description.
* @param int $slotnumber the index of the slot in question.
* @return bool whether that question is a real question.
*/
public function is_real_question($slotnumber) {
return $this->get_question_in_slot($slotnumber)->length != 0;
}
/**
* Get the course id that the quiz belongs to.
* @return int the course.id for the quiz.
*/
public function get_courseid() {
return $this->quizobj->get_courseid();
}
/**
* Get the course module id of the quiz.
* @return int the course_modules.id for the quiz.
@@ -258,18 +364,18 @@ class structure {
}
/**
* Get all the questions in a section of the quiz.
* Get all the slots in a section of the quiz.
* @param int $sectionid the section id.
* @return \stdClass[] of question/slot objects.
* @return int[] slot numbers.
*/
public function get_questions_in_section($sectionid) {
$questions = array();
public function get_slots_in_section($sectionid) {
$slots = array();
foreach ($this->slotsinorder as $slot) {
if ($slot->sectionid == $sectionid) {
$questions[] = $this->questions[$slot->questionid];
$slots[] = $slot->slot;
}
}
return $questions;
return $slots;
}
/**
@@ -280,6 +386,39 @@ class structure {
return $this->sections;
}
/**
* Get the overall quiz grade formatted for display.
* @return string the maximum grade for this quiz.
*/
public function formatted_quiz_grade() {
return quiz_format_grade($this->get_quiz(), $this->get_quiz()->grade);
}
/**
* Get the maximum mark for a question, formatted for display.
* @param int $slotnumber the index of the slot in question.
* @return string the maximum mark for the question in this slot.
*/
public function formatted_question_grade($slotnumber) {
return quiz_format_question_grade($this->get_quiz(), $this->slotsinorder[$slotnumber]->maxmark);
}
/**
* Get the number of decimal places for displyaing overall quiz grades or marks.
* @return int the number of decimal places.
*/
public function get_decimal_places_for_grades() {
return $this->get_quiz()->decimalpoints;
}
/**
* Get the number of decimal places for displyaing question marks.
* @return int the number of decimal places.
*/
public function get_decimal_places_for_question_marks() {
return quiz_get_grade_format($this->get_quiz());
}
/**
* Get any warnings to show at the top of the edit page.
* @return string[] array of strings.
@@ -453,11 +592,10 @@ class structure {
protected function populate_question_numbers() {
$number = 1;
foreach ($this->slots as $slot) {
$question = $this->questions[$slot->questionid];
if ($question->length == 0) {
$question->displayednumber = get_string('infoshort', 'quiz');
if ($this->questions[$slot->questionid]->length == 0) {
$slot->displayednumber = get_string('infoshort', 'quiz');
} else {
$question->displayednumber = $number;
$slot->displayednumber = $number;
$number += 1;
}
}
@@ -661,21 +799,13 @@ class structure {
}
/**
* Change require previous for a slot..
* @param \stdClass $slot row from the quiz_slots table.
* Set whether the question in a particular slot requires the previous one.
* @param int $slotid id of slot.
* @param bool $requireprevious if true, set this question to require the previous one.
*/
public function update_question_dependency($slot) {
public function update_question_dependency($slotid, $requireprevious) {
global $DB;
$trans = $DB->start_delegated_transaction();
// Swap dependency setting.
if ($slot->requireprevious == 1) {
$slot->requireprevious = 0;
} else {
$slot->requireprevious = 1;
}
$DB->update_record('quiz_slots', $slot);
$trans->allow_commit();
$DB->set_field('quiz_slots', 'requireprevious', $requireprevious, array('id' => $slotid));
}
/**
+8 -4
View File
@@ -402,6 +402,10 @@ function xmldb_quiz_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2013031900, 'quiz');
}
// Moodle v2.5.0 release upgrade line.
// Put any upgrade step following this.
// Moodle v2.6.0 release upgrade line.
// Put any upgrade step following this.
@@ -803,10 +807,10 @@ function xmldb_quiz_upgrade($oldversion) {
// Moodle v2.8.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2014111000) {
if ($oldversion < 2015022600) {
// Define field requireprevious to be added to quiz_slots.
$table = new xmldb_table('quiz_slots');
$field = new xmldb_field('requireprevious', XMLDB_TYPE_INTEGER, '4', null, null, null, null, 'maxmark');
$field = new xmldb_field('requireprevious', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, 0, 'page');
// Conditionally launch add field page.
if (!$dbman->field_exists($table, $field)) {
@@ -814,8 +818,8 @@ function xmldb_quiz_upgrade($oldversion) {
}
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2014111000, 'quiz');
upgrade_mod_savepoint(true, 2015022600, 'quiz');
}
return true;
}
+9 -3
View File
@@ -106,6 +106,7 @@ switch($requestmethod) {
echo json_encode(array('instancemaxmark' => quiz_format_question_grade($quiz, $maxmark),
'newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades)));
break;
case 'updatepagebreak':
require_capability('mod/quiz:manage', $modcontext);
$slots = $structure->update_page_break($quiz, $id, $value);
@@ -116,10 +117,15 @@ switch($requestmethod) {
}
echo json_encode(array('slots' => $json));
break;
}
break;
case 'course':
case 'updatedependency':
require_capability('mod/quiz:manage', $modcontext);
$slot = $structure->get_slot_by_id($id);
$value = (bool) $value;
$structure->update_question_dependency($slot->id, $value);
echo json_encode(array('requireprevious' => $value));
break;
}
break;
}
break;
+4 -6
View File
@@ -45,7 +45,6 @@ $string['addpagebreak'] = 'Add page break';
$string['addpagehere'] = 'Add page here';
$string['addquestion'] = 'Add question';
$string['addquestionfrombanktopage'] = 'Add from the question bank to page {$a}';
$string['addquestiondependency'] = 'Add question dependency';
$string['addquestions'] = 'Add questions';
$string['addquestionstoquiz'] = 'Add questions to current quiz';
$string['addrandom'] = 'Add {$a} random questions';
@@ -616,10 +615,10 @@ $string['questionbankmanagement'] = 'Question bank management';
$string['questionbehaviour'] = 'Question behaviour';
$string['questioncats'] = 'Question categories';
$string['questiondeleted'] = 'This question has been deleted. Please contact your teacher';
$string['questiondependency'] = 'Question dependency';
$string['configquestiondependency'] = 'Question dependency can be used for displaying question n+1 only if students have completed question n, because question n +1 may exposed the answer for question n.';
$string['questiondependency_help'] = 'Question dependency can be used for displaying question n+1 only if students have completed question n, because question n +1 may exposed the answer for question n.';
$string['questiondependsonprevious'] = 'You have to complete the previous question first, then you would be able to see the content of this question.';
$string['questiondependencyadd'] = 'No restriction on when question {$a->thisq} can be attempted • Click to change';
$string['questiondependencyfree'] = 'No restriction on this question';
$string['questiondependencyremove'] = 'Question {$a->thisq} cannot be attempted until the previous question {$a->previousq} has been completed • Click to change';
$string['questiondependsonprevious'] = 'This question cannot be attempted until the previous question has been completed.';
$string['questioninuse'] = 'The question \'{$a->questionname}\' is currently being used in: <br />{$a->quiznames}<br />The question will not be deleted from these quizzes but only from the category list.';
$string['questionmissing'] = 'Question for this session is missing';
$string['questionname'] = 'Question name';
@@ -700,7 +699,6 @@ $string['removeallquizattempts'] = 'Delete all quiz attempts';
$string['removeemptypage'] = 'Remove empty page';
$string['removepagebreak'] = 'Remove page break';
$string['removeselected'] = 'Remove selected';
$string['removequestiondependency'] = 'Remove question dependency';
$string['rename'] = 'Rename';
$string['renderingserverconnectfailed'] = 'The server {$a} failed to process an RQP request. Check that the URL is correct.';
$string['reorderquestions'] = 'Reorder questions';
-43
View File
@@ -1,43 +0,0 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Set question dependency.
*
* @package mod_quiz
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
$cmid = required_param('cmid', PARAM_INT);
$quizid = required_param('quizid', PARAM_INT);
$slotid = required_param('slotid', PARAM_INT);
require_sesskey();
$quizobj = quiz::create($quizid);
require_login($quizobj->get_course(), false, $quizobj->get_cm());
require_capability('mod/quiz:manage', $quizobj->get_context());
$structure = $quizobj->get_structure();
// Update dependency settings on this slot.
$slot = $structure->get_slot_by_id($slotid);
$structure->update_question_dependency($slot);
redirect(new moodle_url('edit.php', array('cmid' => $quizobj->get_cmid())));
+4 -7
View File
@@ -456,14 +456,11 @@ class mod_quiz_renderer extends plugin_renderer_base {
'enctype' => 'multipart/form-data', 'accept-charset' => 'utf-8',
'id' => 'responseform'));
$output .= html_writer::start_tag('div');
// Print all the questions.
foreach ($slots as $slot) {
$requireprevious = $attemptobj->require_previous_question($slot);
if ($requireprevious) {
$output .= $requireprevious;
} else {
$output .= $attemptobj->render_question($slot, false,
$attemptobj->attempt_url($slot, $page));
}
$output .= $attemptobj->render_question($slot, false,
$attemptobj->attempt_url($slot, $page));
}
$output .= html_writer::start_tag('div', array('class' => 'submitbtns'));
+1 -2
View File
@@ -25,7 +25,6 @@
require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
$cmid = required_param('cmid', PARAM_INT);
$quizid = required_param('quizid', PARAM_INT);
$slotnumber = required_param('slot', PARAM_INT);
$repagtype = required_param('repag', PARAM_INT);
@@ -38,7 +37,7 @@ if (quiz_has_attempts($quizid)) {
$reportlink = quiz_attempt_summary_link_to_reports($quizobj->get_quiz(),
$quizobj->get_cm(), $quizobj->get_context());
throw new \moodle_exception('cannoteditafterattempts', 'quiz',
new moodle_url('/mod/quiz/edit.php', array('cmid' => $cmid)), $reportlink);
new moodle_url('/mod/quiz/edit.php', array('cmid' => $quizobj->get_cmid())), $reportlink);
}
$slotnumber++;
+44 -30
View File
@@ -24,6 +24,12 @@
text-align: right;
}
#page-mod-quiz-attempt .mod_quiz-blocked_question_warning .que .formulation,
#page-mod-quiz-review .mod_quiz-blocked_question_warning .que .formulation {
background: #eee;
border: 1px solid #dcdcdc;
}
body.jsenabled .questionflagcheckbox {
display: none;
}
@@ -495,47 +501,32 @@ table.quizreviewsummary td.cell {
}
/** Mod quiz edit **/
#page-mod-quiz-edit h2.main {
display: inline;
padding-right: 1em;
clear: left;
}
#page-mod-quiz-edit.dir-rtl h2.main {
padding-left: 1em;
padding-right: 0;
}
#page-mod-quiz-edit .statusbar {
margin: 0.6em 0.4em;
}
#page-mod-quiz-edit .statusdisplay {
background-color: #ffc;
clear: both;
margin: 0.3em 1em 0.3em 0;
padding: 1px ;
/* Stop margin collapse. */
}
#page-mod-quiz-edit.dir-rtl .statusdisplay {
margin: 0.3em 0 0.3em 1em;
margin: 0.3em 0;
padding: 1px 10px;
}
#page-mod-quiz-edit .statusdisplay p {
margin: 0.4em;
margin: 4px 0;
}
#page-mod-quiz-edit .maxgrade,
#page-mod-quiz-edit .totalpoints {
display: block;
float: right;
margin: -2.5em 1em 0em 1em;
margin: -2.5em 0 0;
padding: .2em;
}
#page-mod-quiz-edit .maxgrade label {
display: inline;
}
#page-mod-quiz-edit.dir-rtl .maxgrade,
#page-mod-quiz-edit.dir-rtl .totalpoints {
float: left;
}
#page-mod-quiz-edit .maxgrade label {
display: inline;
}
#page-mod-quiz-edit li.activity > div,
#page-mod-quiz-edit li.pagenumber {
@@ -545,6 +536,7 @@ table.quizreviewsummary td.cell {
#page-mod-quiz-edit .last-add-menu {
position: relative;
height: 1.5em;
margin: 0 20px;
}
#page-mod-quiz-edit .add-menu-outer {
position: absolute;
@@ -564,12 +556,16 @@ table.quizreviewsummary td.cell {
display: inline-block;
}
#page-mod-quiz-edit ul.section {
margin: 0;
padding: 0 20px;
}
#page-mod-quiz-edit ul.slots li.section {
border: 0;
}
#page-mod-quiz-edit ul.slots li.section .content {
background-color:#FAFAFA;
padding:5px 10px;
padding: 0;
}
#page-mod-quiz-edit ul.slots li.section .content h3 {
margin: 0;
@@ -587,7 +583,7 @@ table.quizreviewsummary td.cell {
}
#page-mod-quiz-edit ul.slots li.section {
list-style: none;
margin: 0 0 5px 0;
margin: 0;
padding: 0;
}
#page-mod-quiz-edit ul.slots li.section .left {
@@ -611,7 +607,7 @@ table.quizreviewsummary td.cell {
}
#page-mod-quiz-edit ul.slots li.section li.activity {
background: #E6E6E6;
margin: 3px 0 3px 0;
margin: 3px 0;
padding: 0.2em;
}
#page-mod-quiz-edit ul.slots li.section li.activity.page {
@@ -690,11 +686,29 @@ table.quizreviewsummary td.cell {
margin: 0 2px;
}
#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_icon {
-position: relative;
vertical-align: top;
margin-left: 5px;
margin-right: 5px;
#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper {
position: absolute;
top: 0;
right: 0;
}
#page-mod-quiz-edit.dir-rtl ul.slots li.section li.activity .question_dependency_wrapper {
left: 0;
right: auto;
}
#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper.question_dependency_cannot_depend {
display: none;
}
#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper .currentlink,
#page-mod-quiz-edit ul.slots li.section li.activity .question_dependency_wrapper .cm-edit-action {
position: relative;
left: 20px;
top: -1em;
}
#page-mod-quiz-edit.dir-rtl ul.slots li.section li.activity .question_dependency_wrapper .currentlink,
#page-mod-quiz-edit.dir-rtl ul.slots li.section li.activity .question_dependency_wrapper .cm-edit-action {
right: 20px;
left: auto;
}
#page-mod-quiz-edit ul.slots li.section li.activity .activityinstance {
@@ -0,0 +1,218 @@
@mod @mod_quiz
Feature: Attemp a quiz where some questions require that the previous question has been answered.
In order to complete a quiz where questions require previous ones to be complete
As a student
I need later questions to appear once earlier ones have been answered.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student | Student | One | student@moodle.com |
| teacher | Teacher | One | teacher@moodle.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student | C1 | student |
| teacher | C1 | teacher |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Test questions |
@javascript
Scenario: A question that requires the previous one is initally blocked
Given the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 0 |
| TF2 | 1 | 1 |
When I log in as "student"
And I follow "Course 1"
And I follow "Quiz 1"
And I press "Attempt quiz now"
Then I should see "First question"
And I should see "This question cannot be attempted until the previous question has been completed."
And I should not see "Second question"
And I log out
And I log in as "teacher"
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Attempts: 1"
And I follow "Review attempt"
And I should see "First question"
And I should see "This question cannot be attempted until the previous question has been completed."
And I should not see "Second question"
@javascript
Scenario: A question requires the previous one becomes available when the first one is answered
Given the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 0 |
| TF2 | 1 | 1 |
When I log in as "student"
And I follow "Course 1"
And I follow "Quiz 1"
And I press "Attempt quiz now"
And I click on "True" "radio" in the "First question" "question"
And I press "Check"
Then I should see "First question"
And I should not see "This question cannot be attempted until the previous question has been completed."
And I should see "Second question"
@javascript
Scenario: After quiz submitted, all questions show on the review page
Given the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 0 |
| TF2 | 1 | 1 |
When I log in as "student"
And I follow "Course 1"
And I follow "Quiz 1"
And I press "Attempt quiz now"
And I press "Next"
And I press "Submit all and finish"
And I click on "Submit all and finish" "button" in the "Confirmation" "dialogue"
Then the state of "First question" question is shown as "Not answered"
And the state of "Second question" question is shown as "Not answered"
@javascript
Scenario: A questions cannot be blocked in a deferred feedback quiz (despite what is set in the DB).
Given the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | deferredfeedback |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 0 |
| TF2 | 1 | 1 |
When I log in as "student"
And I follow "Course 1"
And I follow "Quiz 1"
And I press "Attempt quiz now"
Then I should see "First question"
And I should see "Second question"
And I should not see "This question cannot be attempted until the previous question has been completed."
@javascript
Scenario: A questions cannot be blocked in a shuffled quiz (despite what is set in the DB).
Given the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour | shufflequestions | questionsperpage |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback | 1 | 2 |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 1 |
| TF2 | 1 | 1 |
When I log in as "student"
And I follow "Course 1"
And I follow "Quiz 1"
And I press "Attempt quiz now"
Then I should see "First question"
And I should see "Second question"
And I should not see "This question cannot be attempted until the previous question has been completed."
@javascript
Scenario: A questions cannot be blocked in sequential quiz (despite what is set in the DB).
Given the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour | navmethod |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback | sequential |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 1 |
| TF2 | 1 | 1 |
When I log in as "student"
And I follow "Course 1"
And I follow "Quiz 1"
And I press "Attempt quiz now"
Then I should see "First question"
And I should see "Second question"
And I should not see "This question cannot be attempted until the previous question has been completed."
@javascript
Scenario: A questions not blocked if the previous one cannot finish, e.g. essay (despite what is set in the DB).
Given the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | essay | Story | First question |
| Test questions | truefalse | TF2 | Second question |
And the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| Story | 1 | 0 |
| TF2 | 1 | 1 |
When I log in as "student"
And I follow "Course 1"
And I follow "Quiz 1"
And I press "Attempt quiz now"
Then I should see "First question"
And I should see "Second question"
And I should not see "This question cannot be attempted until the previous question has been completed."
@javascript
Scenario: A questions not blocked if the previous one cannot finish, e.g. description (despite what is set in the DB).
Given the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | description | Info | Read me |
| Test questions | truefalse | TF1 | First question |
And the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| Info | 1 | 0 |
| TF1 | 1 | 1 |
When I log in as "student"
And I follow "Course 1"
And I follow "Quiz 1"
And I press "Attempt quiz now"
Then I should see "Read me"
And I should see "First question"
And I should not see "This question cannot be attempted until the previous question has been completed."
+15 -1
View File
@@ -44,13 +44,14 @@ class behat_mod_quiz extends behat_question_base {
* Put the specified questions on the specified pages of a given quiz.
*
* The first row should be column names:
* | question | page | maxmark |
* | question | page | maxmark | requireprevious |
* The first two of those are required. The others are optional.
*
* question needs to uniquely match a question name.
* page is a page number. Must start at 1, and on each following
* row should be the same as the previous, or one more.
* maxmark What the question is marked out of. Defaults to question.defaultmark.
* requireprevious The question can only be attempted after the previous one was completed.
*
* Then there should be a number of rows of data, one for each question you want to add.
*
@@ -130,6 +131,19 @@ class behat_mod_quiz extends behat_question_base {
// Add the question.
quiz_add_quiz_question($questionid, $quiz, $page, $maxmark);
// Require previous.
if (array_key_exists('requireprevious', $questiondata)) {
if ($questiondata['requireprevious'] === '1') {
$slot = $DB->get_field('quiz_slots', 'MAX(slot)', array('quizid' => $quiz->id));
$DB->set_field('quiz_slots', 'requireprevious', 1,
array('quizid' => $quiz->id, 'slot' => $slot));
} else if ($questiondata['requireprevious'] !== '' && $questiondata['requireprevious'] !== '0') {
throw new ExpectationException('Require previous for question "' .
$questiondata['question'] . '" should be 0, 1 or blank.',
$this->getSession());
}
}
}
quiz_update_sumgrades($quiz);
@@ -1,83 +0,0 @@
@mod @mod_quiz @questiondependency
Feature: Edit quiz page - pagination
In order to build a quiz laid out in pages with n question(s) on each page, where n >=1.
I need to be able to add and remove question dependency on any qualified question
in quiz editing page.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | T1 | Teacher1 | teacher1@moodle.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 "activities" exist:
| activity | name | intro | course | idnumber |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 |
When I log in as "teacher1"
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
@javascript
Scenario: There is no dependency setting on question in the quiz.
I can add or remove dependency to a given question by clicking on "Add question dependency"
or "Remove question dependency" icons. Then I can attempt the quiz and see the effect of the
dependency settings.
Then I should see "Editing quiz: Quiz 1"
# Add the first true false question.
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | TF 001 |
| Question text | Answer the TF 001 question |
| General feedback | Thank you, this is the general feedback |
| Correct answer | False |
| Feedback for the response 'True'. | So you think it is true |
| Feedback for the response 'False'. | So you think it is false |
And I should see "TF 001"
# Add the second true false question.
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | TF 002 |
| Question text | Answer the TF 002 question |
| General feedback | Thank you, this is the general feedback |
| Correct answer | False |
| Feedback for the response 'True'. | So you think it is true |
| Feedback for the response 'False'. | So you think it is false |
And I should see "TF 001"
#And I should not see "Add question dependency" "link" in the "TF 001" "table_row"
And I should see "TF 002"
And I follow "Add question dependency"
# Add the third true false question.
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | TF 003 |
| Question text | Answer the TF 003 question |
| General feedback | Thank you, this is the general feedback |
| Correct answer | False |
| Feedback for the response 'True'. | So you think it is true |
| Feedback for the response 'False'. | So you think it is false |
And I should see "TF 001"
And I should see "TF 002"
And I should see "TF 003"
# Attempt the quiz
And I follow "Quiz 1"
When I press "Preview quiz now"
And I should see "You have to complete the previous question first, then you would be able to see the content of this question."
# Back to the quiz editing page
And I follow "Quiz 1"
When I follow "Edit quiz"
Then I should see "Editing quiz: Quiz 1"
And I follow "Remove question dependency"
And I follow "Quiz 1"
When I press "Continue the last preview"
And I press "Start a new preview"
And I should not see "You have to complete the previous question first, then you would be able to see the content of this question."
@@ -0,0 +1,212 @@
@mod @mod_quiz
Feature: Edit quizzes where some questions require the previous one to have been completed
In order to create quizzes where later questions can only be seen after earlier ones are answered
As a teacher
I need to be able to configure this on the Edit quiz page
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | T1 | Teacher1 | teacher1@moodle.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 I log in as "teacher1"
@javascript
Scenario: The first question cannot depend on the previous (whatever is in the DB)
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 1 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
Then "be attempted" "link" should not be visible
# The text "be attempted" is used as a relatively unique string in both the add and remove links.
@javascript
Scenario: If the second question depends on the first, that is shown
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 0 |
| TF2 | 1 | 1 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
Then "This question cannot be attempted until the previous question has been completed." "link" should be visible
@javascript
Scenario: The second question can be set to depend on the first
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
| Test questions | truefalse | TF3 | Third question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 0 |
| TF2 | 1 | 0 |
| TF3 | 1 | 0 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
When I follow "No restriction on when question 2 can be attempted Click to change"
Then "Question 2 cannot be attempted until the previous question 1 has been completed Click to change" "link" should be visible
And "No restriction on when question 3 can be attempted Click to change" "link" should be visible
@javascript
Scenario: A question that did depend on the previous can be un-linked
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
| Test questions | truefalse | TF3 | Third question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 0 |
| TF2 | 1 | 1 |
| TF3 | 1 | 1 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
When I follow "Question 3 cannot be attempted until the previous question 2 has been completed Click to change"
Then "Question 2 cannot be attempted until the previous question 1 has been completed Click to change" "link" should be visible
And "No restriction on when question 3 can be attempted Click to change" "link" should be visible
@javascript
Scenario: Question dependency cannot apply to deferred feedback quizzes so UI is hidden
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | deferredfeedback |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 0 |
| TF2 | 1 | 1 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
Then "be attempted" "link" in the "TF2" "list_item" should not be visible
@javascript
Scenario: Question dependency cannot apply to quizzes where the questions are shuffled so UI is hidden
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour | shufflequestions | questionsperpage |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback | 1 | 2 |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 1 |
| TF2 | 1 | 1 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
Then "be attempted" "link" in the "TF2" "list_item" should not be visible
@javascript
Scenario: Question dependency cannot apply to quizzes with sequential navigation so UI is hidden
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour | navmethod |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback | sequential |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 1 |
| TF2 | 1 | 1 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
Then "be attempted" "link" in the "TF2" "list_item" should not be visible
@javascript
Scenario: A question can never depend on an essay
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | essay | Story | First question |
| Test questions | truefalse | TF1 | First question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| Story | 1 | 0 |
| TF1 | 1 | 0 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
Then "be attempted" "link" in the "TF1" "list_item" should not be visible
@javascript
Scenario: A question can never depend on a description
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | description | Info | Read me |
| Test questions | truefalse | TF1 | First question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| Info | 1 | 0 |
| TF1 | 1 | 0 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
Then "be attempted" "link" in the "TF1" "list_item" should not be visible
@javascript
Scenario: When questions are reordered, the dependency icons are updated correctly
Given the following "activities" exist:
| activity | name | intro | course | idnumber | preferredbehaviour |
| quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 | immediatefeedback |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Test questions | truefalse | TF1 | First question |
| Test questions | truefalse | TF2 | Second question |
| Test questions | truefalse | TF3 | Third question |
And quiz "Quiz 1" contains the following questions:
| question | page | requireprevious |
| TF1 | 1 | 0 |
| TF2 | 1 | 1 |
| TF3 | 1 | 1 |
And I follow "Course 1"
And I follow "Quiz 1"
And I follow "Edit quiz"
When I move "Question 1" to "After Question 3" in the quiz by clicking the move icon
Then "Question 2 cannot be attempted until the previous question 1 has been completed Click to change" "link" should be visible
And "No restriction on when question 3 can be attempted Click to change" "link" should be visible
And "be attempted" "link" in the "TF2" "list_item" should not be visible
+33 -1
View File
@@ -50,7 +50,7 @@ class mod_quiz_structure_testcase extends advanced_testcase {
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quizgenerator->create_instance(array('course' => $course->id, 'questionsperpage' => 0,
'grade' => 100.0, 'sumgrades' => 2));
'grade' => 100.0, 'sumgrades' => 2, 'preferredbehaviour' => 'immediatefeedback'));
$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
@@ -405,4 +405,36 @@ class mod_quiz_structure_testcase extends advanced_testcase {
quiz_add_quiz_question($numq->id, $quiz, $pagenumber);
}
}
/**
* Test updating pagebreaks in the quiz.
*/
public function test_update_question_dependency() {
// Create a test quiz with 8 questions.
list($quiz, $cm, $course) = $this->prepare_quiz_data();
$this->add_eight_questions_to_the_quiz($quiz);
$quizobj = new quiz($quiz, $cm, $course);
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
// Store the original order of slots, so we can assert what has changed.
$originalslotids = array();
foreach ($structure->get_slots() as $slot) {
$originalslotids[$slot->slot] = $slot->id;
}
// Test adding a dependency.
$slotid = $structure->get_slot_id_for_slot(3);
$structure->update_question_dependency($slotid, true);
// Having called update page break, we need to reload $structure.
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
$this->assertEquals(1, $structure->is_question_dependent_on_previous_slot(3));
// Test removing a dependency.
$structure->update_question_dependency($slotid, false);
// Having called update page break, we need to reload $structure.
$structure = \mod_quiz\structure::create_for_quiz($quizobj);
$this->assertEquals(0, $structure->is_question_dependent_on_previous_slot(3));
}
}
+12
View File
@@ -1,5 +1,17 @@
This files describes API changes in the quiz code.
=== 2.9 ===
* There have been changes in classes/output/edit_renderer.php for MDL-40990.
+ Some methods use to take $structure & $question as the first two arguments.
They now take $structure & $slot number. If you need $question, you can get
it using $question = $structure->get_question_in_slot($slot);
+ Some methods used to take $quiz & $question. They now take $structure & $slot
number. You can get $question as above. $quiz is $structure->get_quiz().
+ initialise_editing_javascript has had some redundant arguments removed.
Hopefully, with these changes, we will have less need to make other changes in future.
=== 2.8 ===
* Classes that were defined in various lib files have been moved to the classes
+3 -3
View File
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2014111000; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2014110400; // Requires this Moodle version.
$plugin->component = 'mod_quiz'; // Full name of the plugin (used for diagnostics).
$plugin->version = 2015030500;
$plugin->requires = 2014110400;
$plugin->component = 'mod_quiz';
$plugin->cron = 60;
@@ -11,7 +11,7 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
*/
// The CSS classes we use.
var CSS = {
var CSS = {
ACTIVITYINSTANCE : 'activityinstance',
AVAILABILITYINFODIV : 'div.availabilityinfo',
CONTENTWITHOUTLINK : 'contentwithoutlink',
@@ -24,7 +24,6 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
JOIN: 'page_join',
MODINDENTCOUNT : 'mod-indent-',
MODINDENTHUGE : 'mod-indent-huge',
MODULEIDPREFIX : 'slot-',
PAGE: 'page',
SECTIONHIDDENCLASS : 'hidden',
SECTIONIDPREFIX : 'section-',
@@ -38,7 +37,6 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
ACTIONLINKTEXT : '.actionlinktext',
ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_maxmark',
ACTIVITYFORM : 'span.instancemaxmarkcontainer form',
ACTIVITYICON : 'img.activityicon',
ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
ACTIVITYLI : 'li.activity',
@@ -58,7 +56,6 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
PAGELI : 'li.page',
SECTIONUL : 'ul.section',
SHOW : 'a.' + CSS.SHOW,
SHOWHIDE : 'a.editing_showhide',
SLOTLI : 'li.slot',
SUMMARKS : '.mod_quiz_summarks'
},
@@ -276,8 +273,8 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
*/
initializer: function() {
M.mod_quiz.quizbase.register_module(this);
BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.DEPENDENCY_LINK, this);
},
/**
@@ -322,6 +319,11 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// The user is adding or removing a page break.
this.update_page_break(ev, node, activity, action);
break;
case 'adddependency':
case 'removedependency':
// The user is adding or removing a dependency between questions.
this.update_dependency(ev, node, activity, action);
break;
default:
// Nothing to do here!
break;
@@ -390,8 +392,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
} else {
window.location.reload(true);
}
});
@@ -414,8 +414,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
*/
edit_maxmark : function(ev, button, activity) {
// Get the element we're working on
var activityid = Y.Moodle.mod_quiz.util.slot.getId(activity),
instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
var instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
currentmaxmark = instancemaxmark.get('firstChild'),
oldmaxmark = currentmaxmark.get('data'),
@@ -425,7 +424,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
data = {
'class' : 'resource',
'field' : 'getmaxmark',
'id' : activityid
'id' : Y.Moodle.mod_quiz.util.slot.getId(activity)
};
// Prevent the default actions.
@@ -575,6 +574,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
* @param {String} action The action, addpagebreak or removepagebreak.
* @chainable
*/
update_page_break: function(ev, button, activity, action) {
@@ -582,21 +582,16 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
ev.preventDefault();
var nextactivity = activity.next('li.activity.slot');
var spinner = this.add_spinner(nextactivity),
slotid = 0;
var spinner = this.add_spinner(nextactivity);
var value = action === 'removepagebreak' ? 1 : 2;
var data = {
'class': 'resource',
'field': 'updatepagebreak',
'id': slotid,
'id': Y.Moodle.mod_quiz.util.slot.getId(nextactivity),
'value': value
};
slotid = Y.Moodle.mod_quiz.util.slot.getId(nextactivity);
if (slotid) {
data.id = Number(slotid);
}
this.send_request(data, spinner, function(response) {
if (response.slots) {
if (action === 'addpagebreak') {
@@ -606,8 +601,39 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
Y.Moodle.mod_quiz.util.page.remove(page, true);
}
this.reorganise_edit_page();
} else {
window.location.reload(true);
}
});
return this;
},
/**
* Updates a slot to either require the question in the previous slot to
* have been answered, or not,
*
* @protected
* @method update_page_break
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
* @param {String} action The action, adddependency or removedependency.
* @chainable
*/
update_dependency: function(ev, button, activity, action) {
// Prevent the default button action.
ev.preventDefault();
var spinner = this.add_spinner(activity);
var data = {
'class': 'resource',
'field': 'updatedependency',
'id': Y.Moodle.mod_quiz.util.slot.getId(activity),
'value': action === 'adddependency' ? 1 : 0
};
this.send_request(data, spinner, function(response) {
if (response.hasOwnProperty('requireprevious')) {
Y.Moodle.mod_quiz.util.slot.updateDependencyIcon(activity, response.requireprevious);
}
});
@@ -624,6 +650,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
Y.Moodle.mod_quiz.util.slot.reorderSlots();
Y.Moodle.mod_quiz.util.slot.reorderPageBreaks();
Y.Moodle.mod_quiz.util.page.reorderPages();
Y.Moodle.mod_quiz.util.slot.updateAllDependencyIcons();
},
NAME : 'mod_quiz-resource-toolbox',
@@ -677,134 +704,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
*/
initializer : function() {
M.mod_quiz.quizbase.register_module(this);
// Section Highlighting.
Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
// Section Visibility.
Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
},
toggle_hide_section : function(e) {
// Prevent the default button action.
e.preventDefault();
// Get the section we're working on.
var section = e.target.ancestor(M.mod_quiz.format.get_section_selector(Y)),
button = e.target.ancestor('a', true),
hideicon = button.one('img'),
// The value to submit
value,
// The text for strings and images. Also determines the icon to display.
action,
nextaction;
if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
section.addClass(CSS.SECTIONHIDDENCLASS);
value = 0;
action = 'hide';
nextaction = 'show';
} else {
section.removeClass(CSS.SECTIONHIDDENCLASS);
value = 1;
action = 'show';
nextaction = 'hide';
}
var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
hideicon.setAttrs({
'alt' : newstring,
'src' : M.util.image_url('i/' + nextaction)
});
button.set('title', newstring);
// Change the highlight status
var data = {
'class' : 'section',
'field' : 'visible',
'id' : Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true)),
'value' : value
};
var lightbox = M.util.add_lightbox(Y, section);
lightbox.show();
this.send_request(data, lightbox, function(response) {
var activities = section.all(SELECTOR.ACTIVITYLI);
activities.each(function(node) {
var button;
if (node.one(SELECTOR.SHOW)) {
button = node.one(SELECTOR.SHOW);
} else {
button = node.one(SELECTOR.HIDE);
}
var activityid = Y.Moodle.mod_quiz.util.slot.getId(node);
// NOTE: resourcestotoggle is returned as a string instead
// of a Number so we must cast our activityid to a String.
if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
M.mod_quiz.resource_toolbox.handle_resource_dim(button, node, action);
}
}, this);
});
},
/**
* Toggle highlighting the current section.
*
* @method toggle_highlight
* @param {EventFacade} e
*/
toggle_highlight : function(e) {
// Prevent the default button action.
e.preventDefault();
// Get the section we're working on.
var section = e.target.ancestor(M.mod_quiz.edit.get_section_selector(Y));
var button = e.target.ancestor('a', true);
var buttonicon = button.one('img');
// Determine whether the marker is currently set.
var togglestatus = section.hasClass('current');
var value = 0;
// Set the current highlighted item text.
var old_string = M.util.get_string('markthistopic', 'moodle');
Y.one(SELECTOR.PAGECONTENT)
.all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
.set('title', old_string);
Y.one(SELECTOR.PAGECONTENT)
.all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
.set('alt', old_string)
.set('src', M.util.image_url('i/marker'));
// Remove the highlighting from all sections.
Y.one(SELECTOR.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(Y))
.removeClass('current');
// Then add it if required to the selected section.
if (!togglestatus) {
section.addClass('current');
value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
var new_string = M.util.get_string('markedthistopic', 'moodle');
button
.set('title', new_string);
buttonicon
.set('alt', new_string)
.set('src', M.util.image_url('i/marked'));
}
// Change the highlight status.
var data = {
'class' : 'course',
'field' : 'marker',
'value' : value
};
var lightbox = M.util.add_lightbox(Y, section);
lightbox.show();
this.send_request(data, lightbox);
}
}, {
NAME : 'mod_quiz-section-toolbox',
@@ -814,9 +713,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
},
quizid : {
'value' : 0
},
format : {
'value' : 'topics'
}
}
});
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
*/
// The CSS classes we use.
var CSS = {
var CSS = {
ACTIVITYINSTANCE : 'activityinstance',
AVAILABILITYINFODIV : 'div.availabilityinfo',
CONTENTWITHOUTLINK : 'contentwithoutlink',
@@ -24,7 +24,6 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
JOIN: 'page_join',
MODINDENTCOUNT : 'mod-indent-',
MODINDENTHUGE : 'mod-indent-huge',
MODULEIDPREFIX : 'slot-',
PAGE: 'page',
SECTIONHIDDENCLASS : 'hidden',
SECTIONIDPREFIX : 'section-',
@@ -38,7 +37,6 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
ACTIONLINKTEXT : '.actionlinktext',
ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_maxmark',
ACTIVITYFORM : 'span.instancemaxmarkcontainer form',
ACTIVITYICON : 'img.activityicon',
ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
ACTIVITYLI : 'li.activity',
@@ -58,7 +56,6 @@ YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
PAGELI : 'li.page',
SECTIONUL : 'ul.section',
SHOW : 'a.' + CSS.SHOW,
SHOWHIDE : 'a.editing_showhide',
SLOTLI : 'li.slot',
SUMMARKS : '.mod_quiz_summarks'
},
@@ -276,8 +273,8 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
*/
initializer: function() {
M.mod_quiz.quizbase.register_module(this);
BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.DEPENDENCY_LINK, this);
},
/**
@@ -322,6 +319,11 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// The user is adding or removing a page break.
this.update_page_break(ev, node, activity, action);
break;
case 'adddependency':
case 'removedependency':
// The user is adding or removing a dependency between questions.
this.update_dependency(ev, node, activity, action);
break;
default:
// Nothing to do here!
break;
@@ -390,8 +392,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
} else {
window.location.reload(true);
}
});
@@ -414,8 +414,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
*/
edit_maxmark : function(ev, button, activity) {
// Get the element we're working on
var activityid = Y.Moodle.mod_quiz.util.slot.getId(activity),
instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
var instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
currentmaxmark = instancemaxmark.get('firstChild'),
oldmaxmark = currentmaxmark.get('data'),
@@ -425,7 +424,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
data = {
'class' : 'resource',
'field' : 'getmaxmark',
'id' : activityid
'id' : Y.Moodle.mod_quiz.util.slot.getId(activity)
};
// Prevent the default actions.
@@ -575,6 +574,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
* @param {String} action The action, addpagebreak or removepagebreak.
* @chainable
*/
update_page_break: function(ev, button, activity, action) {
@@ -582,21 +582,16 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
ev.preventDefault();
var nextactivity = activity.next('li.activity.slot');
var spinner = this.add_spinner(nextactivity),
slotid = 0;
var spinner = this.add_spinner(nextactivity);
var value = action === 'removepagebreak' ? 1 : 2;
var data = {
'class': 'resource',
'field': 'updatepagebreak',
'id': slotid,
'id': Y.Moodle.mod_quiz.util.slot.getId(nextactivity),
'value': value
};
slotid = Y.Moodle.mod_quiz.util.slot.getId(nextactivity);
if (slotid) {
data.id = Number(slotid);
}
this.send_request(data, spinner, function(response) {
if (response.slots) {
if (action === 'addpagebreak') {
@@ -606,8 +601,39 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
Y.Moodle.mod_quiz.util.page.remove(page, true);
}
this.reorganise_edit_page();
} else {
window.location.reload(true);
}
});
return this;
},
/**
* Updates a slot to either require the question in the previous slot to
* have been answered, or not,
*
* @protected
* @method update_page_break
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
* @param {String} action The action, adddependency or removedependency.
* @chainable
*/
update_dependency: function(ev, button, activity, action) {
// Prevent the default button action.
ev.preventDefault();
var spinner = this.add_spinner(activity);
var data = {
'class': 'resource',
'field': 'updatedependency',
'id': Y.Moodle.mod_quiz.util.slot.getId(activity),
'value': action === 'adddependency' ? 1 : 0
};
this.send_request(data, spinner, function(response) {
if (response.hasOwnProperty('requireprevious')) {
Y.Moodle.mod_quiz.util.slot.updateDependencyIcon(activity, response.requireprevious);
}
});
@@ -624,6 +650,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
Y.Moodle.mod_quiz.util.slot.reorderSlots();
Y.Moodle.mod_quiz.util.slot.reorderPageBreaks();
Y.Moodle.mod_quiz.util.page.reorderPages();
Y.Moodle.mod_quiz.util.slot.updateAllDependencyIcons();
},
NAME : 'mod_quiz-resource-toolbox',
@@ -677,134 +704,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
*/
initializer : function() {
M.mod_quiz.quizbase.register_module(this);
// Section Highlighting.
Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
// Section Visibility.
Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
},
toggle_hide_section : function(e) {
// Prevent the default button action.
e.preventDefault();
// Get the section we're working on.
var section = e.target.ancestor(M.mod_quiz.format.get_section_selector(Y)),
button = e.target.ancestor('a', true),
hideicon = button.one('img'),
// The value to submit
value,
// The text for strings and images. Also determines the icon to display.
action,
nextaction;
if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
section.addClass(CSS.SECTIONHIDDENCLASS);
value = 0;
action = 'hide';
nextaction = 'show';
} else {
section.removeClass(CSS.SECTIONHIDDENCLASS);
value = 1;
action = 'show';
nextaction = 'hide';
}
var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
hideicon.setAttrs({
'alt' : newstring,
'src' : M.util.image_url('i/' + nextaction)
});
button.set('title', newstring);
// Change the highlight status
var data = {
'class' : 'section',
'field' : 'visible',
'id' : Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true)),
'value' : value
};
var lightbox = M.util.add_lightbox(Y, section);
lightbox.show();
this.send_request(data, lightbox, function(response) {
var activities = section.all(SELECTOR.ACTIVITYLI);
activities.each(function(node) {
var button;
if (node.one(SELECTOR.SHOW)) {
button = node.one(SELECTOR.SHOW);
} else {
button = node.one(SELECTOR.HIDE);
}
var activityid = Y.Moodle.mod_quiz.util.slot.getId(node);
// NOTE: resourcestotoggle is returned as a string instead
// of a Number so we must cast our activityid to a String.
if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
M.mod_quiz.resource_toolbox.handle_resource_dim(button, node, action);
}
}, this);
});
},
/**
* Toggle highlighting the current section.
*
* @method toggle_highlight
* @param {EventFacade} e
*/
toggle_highlight : function(e) {
// Prevent the default button action.
e.preventDefault();
// Get the section we're working on.
var section = e.target.ancestor(M.mod_quiz.edit.get_section_selector(Y));
var button = e.target.ancestor('a', true);
var buttonicon = button.one('img');
// Determine whether the marker is currently set.
var togglestatus = section.hasClass('current');
var value = 0;
// Set the current highlighted item text.
var old_string = M.util.get_string('markthistopic', 'moodle');
Y.one(SELECTOR.PAGECONTENT)
.all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
.set('title', old_string);
Y.one(SELECTOR.PAGECONTENT)
.all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
.set('alt', old_string)
.set('src', M.util.image_url('i/marker'));
// Remove the highlighting from all sections.
Y.one(SELECTOR.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(Y))
.removeClass('current');
// Then add it if required to the selected section.
if (!togglestatus) {
section.addClass('current');
value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
var new_string = M.util.get_string('markedthistopic', 'moodle');
button
.set('title', new_string);
buttonicon
.set('alt', new_string)
.set('src', M.util.image_url('i/marked'));
}
// Change the highlight status.
var data = {
'class' : 'course',
'field' : 'marker',
'value' : value
};
var lightbox = M.util.add_lightbox(Y, section);
lightbox.show();
this.send_request(data, lightbox);
}
}, {
NAME : 'mod_quiz-section-toolbox',
@@ -814,9 +713,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
},
quizid : {
'value' : 0
},
format : {
'value' : 'topics'
}
}
});
@@ -18,7 +18,8 @@ Y.namespace('Moodle.mod_quiz.util.slot');
Y.Moodle.mod_quiz.util.slot = {
CSS: {
SLOT : 'slot',
QUESTIONTYPEDESCRIPTION : 'qtype_description'
QUESTIONTYPEDESCRIPTION : 'qtype_description',
CANNOT_DEPEND: 'question_dependency_cannot_depend'
},
CONSTANTS: {
SLOTIDPREFIX : 'slot-',
@@ -32,7 +33,10 @@ Y.Moodle.mod_quiz.util.slot = {
PAGEBREAK : 'span.page_split_join_wrapper',
ICON : 'img.smallicon',
QUESTIONTYPEDESCRIPTION : '.qtype_description',
SECTIONUL : 'ul.section'
SECTIONUL : 'ul.section',
DEPENDENCY_WRAPPER : '.question_dependency_wrapper',
DEPENDENCY_LINK : '.question_dependency_wrapper .cm-edit-action',
DEPENDENCY_ICON : '.question_dependency_wrapper img'
},
/**
@@ -333,6 +337,65 @@ Y.Moodle.mod_quiz.util.slot = {
// Update the anchor.
pagebreaklink.set('href', newurl);
}, this);
},
/**
* Update the dependency icons.
*
* @method updateAllDependencyIcons
* @return void
*/
updateAllDependencyIcons: function() {
// Get list of slot nodes.
var slots = this.getSlots(),
slotnumber = 0,
previousslot = null;
// Loop through slots incrementing the number each time.
slots.each (function(slot) {
slotnumber++;
if (slotnumber == 1 || previousslot.getData('canfinish') === '0') {
slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).addClass(this.CSS.CANNOT_DEPEND);
} else {
slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).removeClass(this.CSS.CANNOT_DEPEND);
}
this.updateDependencyIcon(slot, null);
previousslot = slot;
}, this);
},
/**
* Update the slot icon to indicate the new requiresprevious state.
*
* @method slot Slot node
* @method requiresprevious Whether this node now requires the previous one.
* @return void
*/
updateDependencyIcon: function(slot, requiresprevious) {
var link = slot.one(this.SELECTORS.DEPENDENCY_LINK);
var icon = slot.one(this.SELECTORS.DEPENDENCY_ICON);
var previousSlot = this.getPrevious(slot);
var a = {thisq: this.getNumber(slot)};
if (previousSlot) {
a.previousq = this.getNumber(previousSlot);
}
if (requiresprevious === null) {
requiresprevious = link.getData('action') === 'removedependency';
}
if (requiresprevious) {
link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a));
link.setData('action', 'removedependency');
icon.set('alt', M.util.get_string('questiondependsonprevious', 'quiz'));
icon.set('src', M.util.image_url('t/locked', 'moodle'));
} else {
link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a));
link.setData('action', 'adddependency');
icon.set('alt', M.util.get_string('questiondependencyfree', 'quiz'));
icon.set('src', M.util.image_url('t/unlocked', 'moodle'));
}
}
};
@@ -1 +1 @@
YUI.add("moodle-mod_quiz-util-slot",function(e,t){e.namespace("Moodle.mod_quiz.util.slot"),e.Moodle.mod_quiz.util.slot={CSS:{SLOT:"slot",QUESTIONTYPEDESCRIPTION:"qtype_description"},CONSTANTS:{SLOTIDPREFIX:"slot-",QUESTION:M.util.get_string("question","moodle")},SELECTORS:{SLOT:"li.slot",INSTANCENAME:".instancename",NUMBER:"span.slotnumber",PAGECONTENT:"div#page-content",PAGEBREAK:"span.page_split_join_wrapper",ICON:"img.smallicon",QUESTIONTYPEDESCRIPTION:".qtype_description",SECTIONUL:"ul.section"},getSlotFromComponent:function(t){return e.one(t).ancestor(this.SELECTORS.SLOT,!0)},getId:function(e){var t=e.get("id").replace(this.CONSTANTS.SLOTIDPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},getName:function(e){var t=e.one(this.SELECTORS.INSTANCENAME);return t?t.get("firstChild").get("data"):null},getNumber:function(e){if(!e)return!1;var t=e.one(this.SELECTORS.NUMBER).get("text").replace(this.CONSTANTS.QUESTION,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},setNumber:function(e,t){var n=e.one(this.SELECTORS.NUMBER);n.setHTML('<span class="accesshide">'+this.CONSTANTS.QUESTION+"</span> "+t)},getSlots:function(){return e.all(this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL+" "+this.SELECTORS.SLOT)},getNumberedSlots:function(){var t=this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL;return t+=" "+this.SELECTORS.SLOT+":not("+this.SELECTORS.QUESTIONTYPEDESCRIPTION+")",e.all(t)},getPrevious:function(e){return e.previous(this.SELECTORS.SLOT)},getPreviousNumbered:function(e){return e.previous(this.SELECTORS.SLOT+":not("+this.SELECTORS.QUESTIONTYPEDESCRIPTION+")")},reorderSlots:function(){var t=this.getSlots();t.each(function(t){if(!e.Moodle.mod_quiz.util.page.getPageFromSlot(t)){var n=t.next(e.Moodle.mod_quiz.util.page.SELECTORS.PAGE);t.swap(n)}var r=this.getPreviousNumbered(t),i=0;if(t.hasClass(this.CSS.QUESTIONTYPEDESCRIPTION))return;r&&(i=this.getNumber(r)),this.setNumber(t,i+1)},this)},remove:function(t){var n=e.Moodle.mod_quiz.util.page.getPageFromSlot(t);t.remove();if(!e.Moodle.mod_quiz.util.page.isEmpty(n))return;e.Moodle.mod_quiz.util.page.remove(n)},getPageBreaks:function(){var t=this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL;return t+=" "+this.SELECTORS.SLOT+this.SELECTORS.PAGEBREAK,e.all(t)},getPageBreak:function(t){return e.one(t).one(this.SELECTORS.PAGEBREAK)},addPageBreak:function(t){var n=M.mod_quiz.resource_toolbox.get("config").addpageiconhtml;n=n.replace("%%SLOT%%",this.getNumber(t));var r=e.Node.create(n);return t.one("div").insert(r,"after"),r},removePageBreak:function(e){var t=this.getPageBreak(e);return t?(t.remove(),!0):!1},reorderPageBreaks:function(){var t=this.getSlots(),n=0;t.each(function(r,i){n++;var s=this.getPageBreak(r);if(!s&&i===t.size()-1)return;!s&&i!==t.size()-1&&(s=this.addPageBreak(r)),s&&i===t.size()-1&&this.removePageBreak(r);var o=s.get("childNodes").item(0),u="",a="";e.Moodle.mod_quiz.util.page.isPage(r.next("li.activity"))?(u="removepagebreak",a="e/remove_page_break"):(u="addpagebreak",a="e/insert_page_break"),o.set("title",M.util.get_string(u,"quiz")),o.setData("action",u);var f=o.one(this.SELECTORS.ICON);f.set("title",M.util.get_string(u,"quiz")),f.set("alt",M.util.get_string(u,"quiz")),f.set("src",M.util.image_url(a));var l=e.QueryString.parse(o.get("href"));l.slot=n;var c="";for(var h in l)c.length&&(c+="&"),c+=h+"="+l[h];o.set("href",c)},this)}}},"@VERSION@",{requires:["node","moodle-mod_quiz-util-base"]});
YUI.add("moodle-mod_quiz-util-slot",function(e,t){e.namespace("Moodle.mod_quiz.util.slot"),e.Moodle.mod_quiz.util.slot={CSS:{SLOT:"slot",QUESTIONTYPEDESCRIPTION:"qtype_description",CANNOT_DEPEND:"question_dependency_cannot_depend"},CONSTANTS:{SLOTIDPREFIX:"slot-",QUESTION:M.util.get_string("question","moodle")},SELECTORS:{SLOT:"li.slot",INSTANCENAME:".instancename",NUMBER:"span.slotnumber",PAGECONTENT:"div#page-content",PAGEBREAK:"span.page_split_join_wrapper",ICON:"img.smallicon",QUESTIONTYPEDESCRIPTION:".qtype_description",SECTIONUL:"ul.section",DEPENDENCY_WRAPPER:".question_dependency_wrapper",DEPENDENCY_LINK:".question_dependency_wrapper .cm-edit-action",DEPENDENCY_ICON:".question_dependency_wrapper img"},getSlotFromComponent:function(t){return e.one(t).ancestor(this.SELECTORS.SLOT,!0)},getId:function(e){var t=e.get("id").replace(this.CONSTANTS.SLOTIDPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},getName:function(e){var t=e.one(this.SELECTORS.INSTANCENAME);return t?t.get("firstChild").get("data"):null},getNumber:function(e){if(!e)return!1;var t=e.one(this.SELECTORS.NUMBER).get("text").replace(this.CONSTANTS.QUESTION,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},setNumber:function(e,t){var n=e.one(this.SELECTORS.NUMBER);n.setHTML('<span class="accesshide">'+this.CONSTANTS.QUESTION+"</span> "+t)},getSlots:function(){return e.all(this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL+" "+this.SELECTORS.SLOT)},getNumberedSlots:function(){var t=this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL;return t+=" "+this.SELECTORS.SLOT+":not("+this.SELECTORS.QUESTIONTYPEDESCRIPTION+")",e.all(t)},getPrevious:function(e){return e.previous(this.SELECTORS.SLOT)},getPreviousNumbered:function(e){return e.previous(this.SELECTORS.SLOT+":not("+this.SELECTORS.QUESTIONTYPEDESCRIPTION+")")},reorderSlots:function(){var t=this.getSlots();t.each(function(t){if(!e.Moodle.mod_quiz.util.page.getPageFromSlot(t)){var n=t.next(e.Moodle.mod_quiz.util.page.SELECTORS.PAGE);t.swap(n)}var r=this.getPreviousNumbered(t),i=0;if(t.hasClass(this.CSS.QUESTIONTYPEDESCRIPTION))return;r&&(i=this.getNumber(r)),this.setNumber(t,i+1)},this)},remove:function(t){var n=e.Moodle.mod_quiz.util.page.getPageFromSlot(t);t.remove();if(!e.Moodle.mod_quiz.util.page.isEmpty(n))return;e.Moodle.mod_quiz.util.page.remove(n)},getPageBreaks:function(){var t=this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL;return t+=" "+this.SELECTORS.SLOT+this.SELECTORS.PAGEBREAK,e.all(t)},getPageBreak:function(t){return e.one(t).one(this.SELECTORS.PAGEBREAK)},addPageBreak:function(t){var n=M.mod_quiz.resource_toolbox.get("config").addpageiconhtml;n=n.replace("%%SLOT%%",this.getNumber(t));var r=e.Node.create(n);return t.one("div").insert(r,"after"),r},removePageBreak:function(e){var t=this.getPageBreak(e);return t?(t.remove(),!0):!1},reorderPageBreaks:function(){var t=this.getSlots(),n=0;t.each(function(r,i){n++;var s=this.getPageBreak(r);if(!s&&i===t.size()-1)return;!s&&i!==t.size()-1&&(s=this.addPageBreak(r)),s&&i===t.size()-1&&this.removePageBreak(r);var o=s.get("childNodes").item(0),u="",a="";e.Moodle.mod_quiz.util.page.isPage(r.next("li.activity"))?(u="removepagebreak",a="e/remove_page_break"):(u="addpagebreak",a="e/insert_page_break"),o.set("title",M.util.get_string(u,"quiz")),o.setData("action",u);var f=o.one(this.SELECTORS.ICON);f.set("title",M.util.get_string(u,"quiz")),f.set("alt",M.util.get_string(u,"quiz")),f.set("src",M.util.image_url(a));var l=e.QueryString.parse(o.get("href"));l.slot=n;var c="";for(var h in l)c.length&&(c+="&"),c+=h+"="+l[h];o.set("href",c)},this)},updateAllDependencyIcons:function(){var e=this.getSlots(),t=0,n=null;e.each(function(e){t++,t==1||n.getData("canfinish")==="0"?e.one(this.SELECTORS.DEPENDENCY_WRAPPER).addClass(this.CSS.CANNOT_DEPEND):e.one(this.SELECTORS.DEPENDENCY_WRAPPER).removeClass(this.CSS.CANNOT_DEPEND),this.updateDependencyIcon(e,null),n=e},this)},updateDependencyIcon:function(e,t){var n=e.one(this.SELECTORS.DEPENDENCY_LINK),r=e.one(this.SELECTORS.DEPENDENCY_ICON),i=this.getPrevious(e),s={thisq:this.getNumber(e)};i&&(s.previousq=this.getNumber(i)),t===null&&(t=n.getData("action")==="removedependency"),t?(n.set("title",M.util.get_string("questiondependencyremove","quiz",s)),n.setData("action","removedependency"),r.set("alt",M.util.get_string("questiondependsonprevious","quiz")),r.set("src",M.util.image_url("t/locked","moodle"))):(n.set("title",M.util.get_string("questiondependencyadd","quiz",s)),n.setData("action","adddependency"),r.set("alt",M.util.get_string("questiondependencyfree","quiz")),r.set("src",M.util.image_url("t/unlocked","moodle")))}}},"@VERSION@",{requires:["node","moodle-mod_quiz-util-base"]});
@@ -18,7 +18,8 @@ Y.namespace('Moodle.mod_quiz.util.slot');
Y.Moodle.mod_quiz.util.slot = {
CSS: {
SLOT : 'slot',
QUESTIONTYPEDESCRIPTION : 'qtype_description'
QUESTIONTYPEDESCRIPTION : 'qtype_description',
CANNOT_DEPEND: 'question_dependency_cannot_depend'
},
CONSTANTS: {
SLOTIDPREFIX : 'slot-',
@@ -32,7 +33,10 @@ Y.Moodle.mod_quiz.util.slot = {
PAGEBREAK : 'span.page_split_join_wrapper',
ICON : 'img.smallicon',
QUESTIONTYPEDESCRIPTION : '.qtype_description',
SECTIONUL : 'ul.section'
SECTIONUL : 'ul.section',
DEPENDENCY_WRAPPER : '.question_dependency_wrapper',
DEPENDENCY_LINK : '.question_dependency_wrapper .cm-edit-action',
DEPENDENCY_ICON : '.question_dependency_wrapper img'
},
/**
@@ -333,6 +337,65 @@ Y.Moodle.mod_quiz.util.slot = {
// Update the anchor.
pagebreaklink.set('href', newurl);
}, this);
},
/**
* Update the dependency icons.
*
* @method updateAllDependencyIcons
* @return void
*/
updateAllDependencyIcons: function() {
// Get list of slot nodes.
var slots = this.getSlots(),
slotnumber = 0,
previousslot = null;
// Loop through slots incrementing the number each time.
slots.each (function(slot) {
slotnumber++;
if (slotnumber == 1 || previousslot.getData('canfinish') === '0') {
slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).addClass(this.CSS.CANNOT_DEPEND);
} else {
slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).removeClass(this.CSS.CANNOT_DEPEND);
}
this.updateDependencyIcon(slot, null);
previousslot = slot;
}, this);
},
/**
* Update the slot icon to indicate the new requiresprevious state.
*
* @method slot Slot node
* @method requiresprevious Whether this node now requires the previous one.
* @return void
*/
updateDependencyIcon: function(slot, requiresprevious) {
var link = slot.one(this.SELECTORS.DEPENDENCY_LINK);
var icon = slot.one(this.SELECTORS.DEPENDENCY_ICON);
var previousSlot = this.getPrevious(slot);
var a = {thisq: this.getNumber(slot)};
if (previousSlot) {
a.previousq = this.getNumber(previousSlot);
}
if (requiresprevious === null) {
requiresprevious = link.getData('action') === 'removedependency';
}
if (requiresprevious) {
link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a));
link.setData('action', 'removedependency');
icon.set('alt', M.util.get_string('questiondependsonprevious', 'quiz'));
icon.set('src', M.util.image_url('t/locked', 'moodle'));
} else {
link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a));
link.setData('action', 'adddependency');
icon.set('alt', M.util.get_string('questiondependencyfree', 'quiz'));
icon.set('src', M.util.image_url('t/unlocked', 'moodle'));
}
}
};
+45 -15
View File
@@ -60,8 +60,8 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
*/
initializer: function() {
M.mod_quiz.quizbase.register_module(this);
BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
Y.delegate('click', this.handle_data_action, BODY, SELECTOR.DEPENDENCY_LINK, this);
},
/**
@@ -106,6 +106,11 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// The user is adding or removing a page break.
this.update_page_break(ev, node, activity, action);
break;
case 'adddependency':
case 'removedependency':
// The user is adding or removing a dependency between questions.
this.update_dependency(ev, node, activity, action);
break;
default:
// Nothing to do here!
break;
@@ -174,8 +179,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
} else {
window.location.reload(true);
}
});
@@ -198,8 +201,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
*/
edit_maxmark : function(ev, button, activity) {
// Get the element we're working on
var activityid = Y.Moodle.mod_quiz.util.slot.getId(activity),
instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
var instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
currentmaxmark = instancemaxmark.get('firstChild'),
oldmaxmark = currentmaxmark.get('data'),
@@ -209,7 +211,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
data = {
'class' : 'resource',
'field' : 'getmaxmark',
'id' : activityid
'id' : Y.Moodle.mod_quiz.util.slot.getId(activity)
};
// Prevent the default actions.
@@ -359,6 +361,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
* @param {String} action The action, addpagebreak or removepagebreak.
* @chainable
*/
update_page_break: function(ev, button, activity, action) {
@@ -366,21 +369,16 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
ev.preventDefault();
var nextactivity = activity.next('li.activity.slot');
var spinner = this.add_spinner(nextactivity),
slotid = 0;
var spinner = this.add_spinner(nextactivity);
var value = action === 'removepagebreak' ? 1 : 2;
var data = {
'class': 'resource',
'field': 'updatepagebreak',
'id': slotid,
'id': Y.Moodle.mod_quiz.util.slot.getId(nextactivity),
'value': value
};
slotid = Y.Moodle.mod_quiz.util.slot.getId(nextactivity);
if (slotid) {
data.id = Number(slotid);
}
this.send_request(data, spinner, function(response) {
if (response.slots) {
if (action === 'addpagebreak') {
@@ -390,8 +388,39 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
Y.Moodle.mod_quiz.util.page.remove(page, true);
}
this.reorganise_edit_page();
} else {
window.location.reload(true);
}
});
return this;
},
/**
* Updates a slot to either require the question in the previous slot to
* have been answered, or not,
*
* @protected
* @method update_page_break
* @param {EventFacade} ev The event that was fired.
* @param {Node} button The button that triggered this action.
* @param {Node} activity The activity node that this action will be performed on.
* @param {String} action The action, adddependency or removedependency.
* @chainable
*/
update_dependency: function(ev, button, activity, action) {
// Prevent the default button action.
ev.preventDefault();
var spinner = this.add_spinner(activity);
var data = {
'class': 'resource',
'field': 'updatedependency',
'id': Y.Moodle.mod_quiz.util.slot.getId(activity),
'value': action === 'adddependency' ? 1 : 0
};
this.send_request(data, spinner, function(response) {
if (response.hasOwnProperty('requireprevious')) {
Y.Moodle.mod_quiz.util.slot.updateDependencyIcon(activity, response.requireprevious);
}
});
@@ -408,6 +437,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
Y.Moodle.mod_quiz.util.slot.reorderSlots();
Y.Moodle.mod_quiz.util.slot.reorderPageBreaks();
Y.Moodle.mod_quiz.util.page.reorderPages();
Y.Moodle.mod_quiz.util.slot.updateAllDependencyIcons();
},
NAME : 'mod_quiz-resource-toolbox',
-131
View File
@@ -33,134 +33,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
*/
initializer : function() {
M.mod_quiz.quizbase.register_module(this);
// Section Highlighting.
Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
// Section Visibility.
Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
},
toggle_hide_section : function(e) {
// Prevent the default button action.
e.preventDefault();
// Get the section we're working on.
var section = e.target.ancestor(M.mod_quiz.format.get_section_selector(Y)),
button = e.target.ancestor('a', true),
hideicon = button.one('img'),
// The value to submit
value,
// The text for strings and images. Also determines the icon to display.
action,
nextaction;
if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
section.addClass(CSS.SECTIONHIDDENCLASS);
value = 0;
action = 'hide';
nextaction = 'show';
} else {
section.removeClass(CSS.SECTIONHIDDENCLASS);
value = 1;
action = 'show';
nextaction = 'hide';
}
var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
hideicon.setAttrs({
'alt' : newstring,
'src' : M.util.image_url('i/' + nextaction)
});
button.set('title', newstring);
// Change the highlight status
var data = {
'class' : 'section',
'field' : 'visible',
'id' : Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true)),
'value' : value
};
var lightbox = M.util.add_lightbox(Y, section);
lightbox.show();
this.send_request(data, lightbox, function(response) {
var activities = section.all(SELECTOR.ACTIVITYLI);
activities.each(function(node) {
var button;
if (node.one(SELECTOR.SHOW)) {
button = node.one(SELECTOR.SHOW);
} else {
button = node.one(SELECTOR.HIDE);
}
var activityid = Y.Moodle.mod_quiz.util.slot.getId(node);
// NOTE: resourcestotoggle is returned as a string instead
// of a Number so we must cast our activityid to a String.
if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
M.mod_quiz.resource_toolbox.handle_resource_dim(button, node, action);
}
}, this);
});
},
/**
* Toggle highlighting the current section.
*
* @method toggle_highlight
* @param {EventFacade} e
*/
toggle_highlight : function(e) {
// Prevent the default button action.
e.preventDefault();
// Get the section we're working on.
var section = e.target.ancestor(M.mod_quiz.edit.get_section_selector(Y));
var button = e.target.ancestor('a', true);
var buttonicon = button.one('img');
// Determine whether the marker is currently set.
var togglestatus = section.hasClass('current');
var value = 0;
// Set the current highlighted item text.
var old_string = M.util.get_string('markthistopic', 'moodle');
Y.one(SELECTOR.PAGECONTENT)
.all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
.set('title', old_string);
Y.one(SELECTOR.PAGECONTENT)
.all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
.set('alt', old_string)
.set('src', M.util.image_url('i/marker'));
// Remove the highlighting from all sections.
Y.one(SELECTOR.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(Y))
.removeClass('current');
// Then add it if required to the selected section.
if (!togglestatus) {
section.addClass('current');
value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
var new_string = M.util.get_string('markedthistopic', 'moodle');
button
.set('title', new_string);
buttonicon
.set('alt', new_string)
.set('src', M.util.image_url('i/marked'));
}
// Change the highlight status.
var data = {
'class' : 'course',
'field' : 'marker',
'value' : value
};
var lightbox = M.util.add_lightbox(Y, section);
lightbox.show();
this.send_request(data, lightbox);
}
}, {
NAME : 'mod_quiz-section-toolbox',
@@ -170,9 +42,6 @@ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
},
quizid : {
'value' : 0
},
format : {
'value' : 'topics'
}
}
});
+1 -4
View File
@@ -9,7 +9,7 @@
*/
// The CSS classes we use.
var CSS = {
var CSS = {
ACTIVITYINSTANCE : 'activityinstance',
AVAILABILITYINFODIV : 'div.availabilityinfo',
CONTENTWITHOUTLINK : 'contentwithoutlink',
@@ -22,7 +22,6 @@
JOIN: 'page_join',
MODINDENTCOUNT : 'mod-indent-',
MODINDENTHUGE : 'mod-indent-huge',
MODULEIDPREFIX : 'slot-',
PAGE: 'page',
SECTIONHIDDENCLASS : 'hidden',
SECTIONIDPREFIX : 'section-',
@@ -36,7 +35,6 @@
ACTIONLINKTEXT : '.actionlinktext',
ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_maxmark',
ACTIVITYFORM : 'span.instancemaxmarkcontainer form',
ACTIVITYICON : 'img.activityicon',
ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
ACTIVITYLI : 'li.activity',
@@ -56,7 +54,6 @@
PAGELI : 'li.page',
SECTIONUL : 'ul.section',
SHOW : 'a.' + CSS.SHOW,
SHOWHIDE : 'a.editing_showhide',
SLOTLI : 'li.slot',
SUMMARKS : '.mod_quiz_summarks'
},
+65 -2
View File
@@ -16,7 +16,8 @@ Y.namespace('Moodle.mod_quiz.util.slot');
Y.Moodle.mod_quiz.util.slot = {
CSS: {
SLOT : 'slot',
QUESTIONTYPEDESCRIPTION : 'qtype_description'
QUESTIONTYPEDESCRIPTION : 'qtype_description',
CANNOT_DEPEND: 'question_dependency_cannot_depend'
},
CONSTANTS: {
SLOTIDPREFIX : 'slot-',
@@ -30,7 +31,10 @@ Y.Moodle.mod_quiz.util.slot = {
PAGEBREAK : 'span.page_split_join_wrapper',
ICON : 'img.smallicon',
QUESTIONTYPEDESCRIPTION : '.qtype_description',
SECTIONUL : 'ul.section'
SECTIONUL : 'ul.section',
DEPENDENCY_WRAPPER : '.question_dependency_wrapper',
DEPENDENCY_LINK : '.question_dependency_wrapper .cm-edit-action',
DEPENDENCY_ICON : '.question_dependency_wrapper img'
},
/**
@@ -331,5 +335,64 @@ Y.Moodle.mod_quiz.util.slot = {
// Update the anchor.
pagebreaklink.set('href', newurl);
}, this);
},
/**
* Update the dependency icons.
*
* @method updateAllDependencyIcons
* @return void
*/
updateAllDependencyIcons: function() {
// Get list of slot nodes.
var slots = this.getSlots(),
slotnumber = 0,
previousslot = null;
// Loop through slots incrementing the number each time.
slots.each (function(slot) {
slotnumber++;
if (slotnumber == 1 || previousslot.getData('canfinish') === '0') {
slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).addClass(this.CSS.CANNOT_DEPEND);
} else {
slot.one(this.SELECTORS.DEPENDENCY_WRAPPER).removeClass(this.CSS.CANNOT_DEPEND);
}
this.updateDependencyIcon(slot, null);
previousslot = slot;
}, this);
},
/**
* Update the slot icon to indicate the new requiresprevious state.
*
* @method slot Slot node
* @method requiresprevious Whether this node now requires the previous one.
* @return void
*/
updateDependencyIcon: function(slot, requiresprevious) {
var link = slot.one(this.SELECTORS.DEPENDENCY_LINK);
var icon = slot.one(this.SELECTORS.DEPENDENCY_ICON);
var previousSlot = this.getPrevious(slot);
var a = {thisq: this.getNumber(slot)};
if (previousSlot) {
a.previousq = this.getNumber(previousSlot);
}
if (requiresprevious === null) {
requiresprevious = link.getData('action') === 'removedependency';
}
if (requiresprevious) {
link.set('title', M.util.get_string('questiondependencyremove', 'quiz', a));
link.setData('action', 'removedependency');
icon.set('alt', M.util.get_string('questiondependsonprevious', 'quiz'));
icon.set('src', M.util.image_url('t/locked', 'moodle'));
} else {
link.set('title', M.util.get_string('questiondependencyadd', 'quiz', a));
link.setData('action', 'adddependency');
icon.set('alt', M.util.get_string('questiondependencyfree', 'quiz'));
icon.set('src', M.util.image_url('t/unlocked', 'moodle'));
}
}
};
-29
View File
@@ -821,35 +821,6 @@ class question_usage_by_activity {
$this->observer->notify_attempt_modified($newqa);
}
/**
* Replace a question with a dummy description question in this usage.
*
* @param object $slot
*/
public function replace_question_with_a_description_qtye($slot) {
global $OUTPUT;
// Create a description qtye for the message.
question_bank::load_question_definition_classes('description');
$q = new qtype_description_question();
$q->id = $slot->questionid;
$q->name = 'Description';
$q->questiontext = get_string('questiondependsonprevious', 'quiz');
$q->generalfeedback = '';
$q->qtype = question_bank::get_qtype('description');
$q->options = new question_display_options();
$q->options->flags = 0;
$oldqa = $this->get_question_attempt($slot->slot);
$newqa = new question_attempt($q, $oldqa->get_usage_id(), $this->observer, $slot->maxmark);
$newqa->get_question()->options->flags = 1;
$newqa->set_database_id($oldqa->get_database_id());
$newqa->set_slot($slot->slot);
$this->questionattempts[$slot->slot] = $newqa;
$this->start_question($slot->slot);
$this->render_question($slot->slot, $q->options);
}
/**
* Regrade all the questions in this usage (without changing their max mark).
* @param bool $finished whether each question should be forced to be finished