MDL-47368 Edit quiz: page should not reload so often

The approach is that we have a new JavaScript function
M.mod_quiz.resource_toolbox.reorganise_edit_page which, after a ajax
action, fixes up everything like page breaks, page and question numbers,
that might now be wrong.

We call that function instead of reloading the page.

Also, there are a lot more Behat tests to verify this works correctly.

AMOS BEGIN
 MOV [joinpages,mod_quiz],[removepagebreak,mod_quiz]
 MOV [splitpages,mod_quiz],[addpagebreak,mod_quiz]
AMOS END
This commit is contained in:
Colin Chambers 2014-10-14 15:41:48 +01:00 committed by Tim Hunt
parent d63a81c507
commit a69f81f0d3
25 changed files with 1842 additions and 360 deletions

View File

@ -83,7 +83,8 @@ 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());
$this->initialise_editing_javascript($quizobj->get_course(), $quizobj->get_quiz(),
$structure, $contexts, $pagevars, $pageurl);
// Include the contents of any other popups required.
if ($structure->can_be_edited()) {
@ -361,6 +362,38 @@ class edit_renderer extends \plugin_renderer_base {
public function question_row(structure $structure, $question, $contexts, $pagevars, $pageurl) {
$output = '';
$output .= $this->page_row($structure, $question, $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));
}
// Question HTML.
$questionhtml = $this->question($structure, $question, $pageurl);
$questionclasses = 'activity ' . $question->qtype . ' qtype_' . $question->qtype . ' slot';
$output .= html_writer::tag('li', $questionhtml . $joinhtml,
array('class' => $questionclasses, 'id' => 'slot-' . $question->slotid));
return $output;
}
/**
* 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 \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) {
$output = '';
// Put page in a span for easier styling.
$page = html_writer::tag('span', get_string('page') . ' ' . $question->page,
array('class' => 'text'));
@ -378,20 +411,6 @@ class edit_renderer extends \plugin_renderer_base {
array('class' => 'pagenumber activity yui3-dd-drop page', 'id' => 'page-' . $question->page));
}
// 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));
}
// Question HTML.
$questionhtml = $this->question($structure, $question, $pageurl);
$questionclasses = 'activity ' . $question->qtype . ' qtype_' . $question->qtype . ' slot';
$output .= html_writer::tag('li', $questionhtml . $joinhtml,
array('class' => $questionclasses, 'id' => 'slot-' . $question->slotid));
return $output;
}
@ -644,8 +663,8 @@ class edit_renderer extends \plugin_renderer_base {
*
* @param \stdClass $quiz the quiz settings from the database.
* @param \stdClass $question data from the question and quiz_slots tables.
* @param string $insertpagebreak if true, show an insert page break icon.
* Else show a join pages icon.
* @param bool $insertpagebreak if true, show an insert page break icon.
* else show a join pages icon.
* @return string HTML to output.
*/
public function page_split_join_button($quiz, $question, $insertpagebreak) {
@ -653,13 +672,13 @@ class edit_renderer extends \plugin_renderer_base {
'slot' => $question->slot, 'repag' => $insertpagebreak ? 2 : 1, 'sesskey' => sesskey()));
if ($insertpagebreak) {
$title = get_string('splitpages', 'quiz');
$title = get_string('addpagebreak', 'quiz');
$image = $this->pix_icon('e/insert_page_break', $title);
$action = 'unlinkpage';
$action = 'addpagebreak';
} else {
$title = get_string('joinpages', 'quiz');
$title = get_string('removepagebreak', 'quiz');
$image = $this->pix_icon('e/remove_page_break', $title);
$action = 'linkpage';
$action = 'removepagebreak';
}
// Disable the link if quiz has attempts.
@ -668,7 +687,7 @@ class edit_renderer extends \plugin_renderer_base {
$disabled = "disabled";
}
return html_writer::span($this->action_link($url, $image, null, array('title' => $title,
'class' => 'page_split_join', 'disabled' => $disabled, 'data-action' => $action)),
'class' => 'page_split_join cm-edit-action', 'disabled' => $disabled, 'data-action' => $action)),
'page_split_join_wrapper');
}
@ -837,17 +856,23 @@ class edit_renderer extends \plugin_renderer_base {
*
* @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) {
protected function initialise_editing_javascript($course, $quiz, 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->pagehtml = $this->new_page_template($structure, $contexts, $pagevars, $pageurl);
$config->addpageiconhtml = $this->add_page_icon_template($structure, $quiz);
// Include toolboxes.
$this->page->requires->yui_module('moodle-mod_quiz-toolboxes',
'M.mod_quiz.init_resource_toolbox',
array(array(
@ -857,6 +882,9 @@ class edit_renderer extends \plugin_renderer_base {
'config' => $config,
))
);
unset($config->pagehtml);
unset($config->addpageiconhtml);
$this->page->requires->yui_module('moodle-mod_quiz-toolboxes',
'M.mod_quiz.init_section_toolbox',
array(array(
@ -868,7 +896,6 @@ class edit_renderer extends \plugin_renderer_base {
))
);
// Include course dragdrop.
$this->page->requires->yui_module('moodle-mod_quiz-dragdrop', 'M.mod_quiz.init_section_dragdrop',
array(array(
'courseid' => $course->id,
@ -900,15 +927,19 @@ class edit_renderer extends \plugin_renderer_base {
'movecontent',
'moveleft',
'movesection',
'page',
'question',
'selectall',
'show',
'tocontent',
), 'moodle');
$this->page->requires->strings_for_js(array(
'addpagebreak',
'confirmremovequestion',
'dragtoafter',
'dragtostart',
'removepagebreak',
), 'quiz');
foreach (\question_bank::get_all_qtypes() as $qtype => $notused) {
@ -918,6 +949,61 @@ class edit_renderer extends \plugin_renderer_base {
return true;
}
/**
* 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 \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 for a new page.
*/
protected function new_page_template(structure $structure,
\question_edit_contexts $contexts, array $pagevars, \moodle_url $pageurl) {
if (!$structure->has_questions()) {
return '';
}
$question = $structure->get_question_in_slot(1);
$pagehtml = $this->page_row($structure, $question, $contexts, $pagevars, $pageurl);
// Normalise the page number.
$pagenumber = $question->page;
$strcontexts = array();
$strcontexts[] = 'page-';
$strcontexts[] = get_string('page') . ' ';
$strcontexts[] = 'addonpage%3D';
$strcontexts[] = 'addonpage=';
$strcontexts[] = 'addonpage="';
$strcontexts[] = get_string('addquestionfrombanktopage', 'quiz', '');
$strcontexts[] = 'data-addonpage%3D';
$strcontexts[] = 'action-menu-';
foreach ($strcontexts as $strcontext) {
$pagehtml = str_replace($strcontext . $pagenumber, $strcontext . '%%PAGENUMBER%%', $pagehtml);
}
return $pagehtml;
}
/**
* 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) {
if (!$structure->has_questions()) {
return '';
}
$question = $structure->get_question_in_slot(1);
$html = $this->page_split_join_button($quiz, $question, true);
return str_replace('&slot=1&', '&slot=%%SLOT%%&', $html);
}
/**
* Return the contents of the question bank, to be displayed in the question-bank pop-up.
*

View File

@ -105,7 +105,7 @@ switch($requestmethod) {
echo json_encode(array('instancemaxmark' => quiz_format_question_grade($quiz, $maxmark),
'newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades)));
break;
case 'linkslottopage':
case 'updatepagebreak':
require_capability('mod/quiz:manage', $modcontext);
$slots = $structure->update_page_break($quiz, $id, $value);
$json = array();
@ -133,7 +133,8 @@ switch($requestmethod) {
$structure->remove_slot($quiz, $slot->slot);
quiz_delete_previews($quiz);
quiz_update_sumgrades($quiz);
echo json_encode(array('newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades)));
echo json_encode(array('newsummarks' => quiz_format_grade($quiz, $quiz->sumgrades),
'deleted' => true));
break;
}
break;

View File

@ -41,6 +41,7 @@ $string['addnewgroupoverride'] = 'Add group override';
$string['addnewpagesafterselected'] = 'Add new pages after selected questions';
$string['addnewquestionsqbank'] = 'Add questions to the category {$a->catname}: {$a->link}';
$string['addnewuseroverride'] = 'Add user override';
$string['addpagebreak'] = 'Add page break';
$string['addpagehere'] = 'Add page here';
$string['addquestion'] = 'Add question';
$string['addquestionfrombanktopage'] = 'Add from the question bank to page {$a}';
@ -431,7 +432,6 @@ $string['invalidquizid'] = 'Invalid quiz ID';
$string['invalidsource'] = 'The source is not accepted as valid.';
$string['invalidsourcetype'] = 'Invalid source type.';
$string['invalidstateid'] = 'Invalid state id';
$string['joinpages'] = 'Remove page break';
$string['lastanswer'] = 'Your last answer was';
$string['layout'] = 'Layout';
$string['layoutasshown'] = 'Page layout as shown.';
@ -694,6 +694,7 @@ $string['regradingquiz'] = 'Regrading quiz "{$a}"';
$string['remove'] = 'Remove';
$string['removeallquizattempts'] = 'Delete all quiz attempts';
$string['removeemptypage'] = 'Remove empty page';
$string['removepagebreak'] = 'Remove page break';
$string['removeselected'] = 'Remove selected';
$string['rename'] = 'Rename';
$string['renderingserverconnectfailed'] = 'The server {$a} failed to process an RQP request. Check that the URL is correct.';
@ -833,7 +834,6 @@ $string['sortsubmit'] = 'Sort questions';
$string['sorttypealpha'] = 'Sort by type, name';
$string['specificapathnotonquestion'] = 'The specified file path is not on the specified question';
$string['specificquestionnotonquiz'] = 'Specified question is not on the specified quiz';
$string['splitpages'] = 'Add page break';
$string['startagain'] = 'Start again';
$string['startattempt'] = 'Start attempt';
$string['startedon'] = 'Started on';
@ -912,7 +912,7 @@ $string['xhtml'] = 'XHTML';
$string['youneedtoenrol'] = 'You need to enrol in this course before you can attempt this quiz';
$string['yourfinalgradeis'] = 'Your final grade for this quiz is {$a}.';
// Deprecated since Moodle 2.8
// Deprecated since Moodle 2.8.
$string['categories'] = 'Categories';
$string['category'] = 'Category';

View File

@ -128,6 +128,22 @@ class behat_mod_quiz extends behat_question_base {
);
}
/**
* Check whether a particular question is not on a particular page of the quiz on the Edit quiz page.
* @Given /^I should not see "(?P<question_name>(?:[^"]|\\")*)" on quiz page "(?P<page_number>\d+)"$/
* @param string $questionname the name of the question we are looking for.
* @param number $pagenumber the page it should be found on.
* @return array of steps.
*/
public function i_should_not_see_on_quiz_page($questionname, $pagenumber) {
$xpath = "//li[contains(., '" . $this->escape($questionname) .
"')][./preceding-sibling::li[contains(@class, 'pagenumber')][1][contains(., 'Page " .
$pagenumber . "')]]";
return array(
new Given('"' . $xpath . '" "xpath_element" should not exist'),
);
}
/**
* Check whether one question comes before another on the Edit quiz page.
* The two questions must be on the same page.
@ -153,13 +169,24 @@ class behat_mod_quiz extends behat_question_base {
* @return array of steps.
*/
public function should_have_number_on_the_edit_quiz_page($questionname, $number) {
$xpath = "//li[contains(@class, ' slot ') and contains(., '" . $this->escape($questionname) .
"')]//span[@class = 'slotnumber' and normalize-space(text()) = '" . $this->escape($number) . "']";
$xpath = "//li[contains(@class, 'slot') and contains(., '" . $this->escape($questionname) .
"')]//span[contains(@class, 'slotnumber') and normalize-space(text()) = '" . $this->escape($number) . "']";
return array(
new Given('"' . $xpath . '" "xpath_element" should exist'),
);
}
/**
* Get the xpath for a partcular add/remove page-break icon.
* @param string $addorremoves 'Add' or 'Remove'.
* @param string $questionname the name of the question before the icon.
* @return string the requried xpath.
*/
protected function get_xpath_page_break_icon_after_question($addorremoves, $questionname) {
return "//li[contains(@class, 'slot') and contains(., '" . $this->escape($questionname) .
"')]//a[contains(@class, 'page_split_join') and @title = '" . $addorremoves . " page break']";
}
/**
* Click the add or remove page-break icon after a particular question.
* @When /^I click on the "(Add|Remove)" page break icon after question "(?P<question_name>(?:[^"]|\\")*)"$/
@ -168,8 +195,50 @@ class behat_mod_quiz extends behat_question_base {
* @return array of steps.
*/
public function i_click_on_the_page_break_icon_after_question($addorremoves, $questionname) {
$xpath = "//li[contains(@class, ' slot ') and contains(., '" . $this->escape($questionname) .
"')]//a[@class = 'page_split_join' and @title = '" . $addorremoves . " page break']";
$xpath = $this->get_xpath_page_break_icon_after_question($addorremoves, $questionname);
return array(
new Given('I click on "' . $xpath . '" "xpath_element"'),
);
}
/**
* Assert the add or remove page-break icon after a particular question exists.
* @When /^the "(Add|Remove)" page break icon after question "(?P<question_name>(?:[^"]|\\")*)" should exist$/
* @param string $addorremoves 'Add' or 'Remove'.
* @param string $questionname the name of the question before the icon to click.
* @return array of steps.
*/
public function the_page_break_icon_after_question_should_exist($addorremoves, $questionname) {
$xpath = $this->get_xpath_page_break_icon_after_question($addorremoves, $questionname);
return array(
new Given('"' . $xpath . '" "xpath_element" should exist'),
);
}
/**
* Assert the add or remove page-break icon after a particular question does not exist.
* @When /^the "(Add|Remove)" page break icon after question "(?P<question_name>(?:[^"]|\\")*)" should not exist$/
* @param string $addorremoves 'Add' or 'Remove'.
* @param string $questionname the name of the question before the icon to click.
* @return array of steps.
*/
public function the_page_break_icon_after_question_should_not_exist($addorremoves, $questionname) {
$xpath = $this->get_xpath_page_break_icon_after_question($addorremoves, $questionname);
return array(
new Given('"' . $xpath . '" "xpath_element" should not exist'),
);
}
/**
* Check the add or remove page-break link after a particular question contains the given parameters in its url.
* @When /^the "(Add|Remove)" page break link after question "(?P<question_name>(?:[^"]|\\")*) should contain:"$/
* @param string $addorremoves 'Add' or 'Remove'.
* @param string $questionname the name of the question before the icon to click.
* @param TableNode $paramdata with data for checking the page break url
* @return array of steps.
*/
public function the_page_break_link_after_question_should_contain($addorremoves, $questionname, $paramdata) {
$xpath = $this->get_xpath_page_break_icon_after_question($addorremoves, $questionname);
return array(
new Given('I click on "' . $xpath . '" "xpath_element"'),
);
@ -210,4 +279,21 @@ class behat_mod_quiz extends behat_question_base {
'and I drop it in "' . $destinationxpath . '" "xpath_element"'),
);
}
/**
* Delete a question on the Edit quiz page by first clicking on the Delete icon,
* then clicking one of the "After ..." links.
* @When /^I delete "(?P<question_name>(?:[^"]|\\")*)" in the quiz by clicking the delete icon$/
* @param string $questionname the name of the question we are looking for.
* @return array of steps.
*/
public function i_delete_question_by_clicking_the_delete_icon($questionname) {
$slotxpath = "//li[contains(@class, ' slot ') and contains(., '" . $this->escape($questionname) .
"')]";
$deletexpath = "//a[contains(@class, 'editing_delete')]";
return array(
new Given('I click on "' . $slotxpath . $deletexpath . '" "xpath_element"'),
new Given('I click on "Yes" "button" in the "Confirm" "dialogue"'),
);
}
}

View File

@ -0,0 +1,118 @@
@mod @mod_quiz
Feature: Edit quiz page - delete
In order to change the layout of a quiz I built
As a teacher
I need to be able to delete questions.
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 | course | idnumber |
| quiz | Quiz 1 | C1 | quiz1 |
And I log in as "teacher1"
And I follow "Course 1"
@javascript
Scenario: Delete questions by clicking on the delete icon.
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | Question A |
| Question text | Answer me |
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | Question B |
| Question text | Answer again |
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | Question C |
| Question text | And again |
And I click on the "Add" page break icon after question "Question B"
# Confirm the starting point.
Then I should see "Question A" on quiz page "1"
And I should see "Question B" on quiz page "1"
And I should see "Question C" on quiz page "2"
And I should see "Total of marks: 3.00"
# Delete last question in last page. Page contains multiple questions
When I delete "Question C" in the quiz by clicking the delete icon
Then I should see "Question A" on quiz page "1"
And I should see "Question B" on quiz page "1"
And I should not see "Question C" on quiz page "2"
And I should see "Total of marks: 2.00"
# Delete last question in last page. The page contains multiple questions and there are multiple pages.
When I click on the "Add" page break icon after question "Question A"
Then I should see "Question B" on quiz page "2"
And the "Remove" page break icon after question "Question A" should exist
And I delete "Question B" in the quiz by clicking the delete icon
Then I should see "Question A" on quiz page "1"
And I should not see "Page 2"
And I should not see "Question B" on quiz page "2"
And the "Remove" page break icon after question "Question A" should not exist
And I should see "Total of marks: 1.00"
# Delete last remaining question in the last remaining page.
And I delete "Question A" in the quiz by clicking the delete icon
Then I should not see "Question A" on quiz page "1"
And I should not see "Page 1"
And I should see "Total of marks: 0.00"
@javascript @edit_quiz_delete_start
Scenario: Delete questions from the start of the list.
# Add more questions.
When I add a "Description" question to the "Quiz 1" quiz with:
| Question name | Question A |
| Question text | Answer A |
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | Question B |
| Question text | Answer B |
And I add a "Description" question to the "Quiz 1" quiz with:
| Question name | Question C |
| Question text | Answer C |
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | Question D |
| Question text | Answer D |
And I add a "True/False" question to the "Quiz 1" quiz with:
| Question name | Question E |
| Question text | Answer E |
Then "Question A" should have number "i" on the edit quiz page
And "Question B" should have number "1" on the edit quiz page
And "Question C" should have number "i" on the edit quiz page
And "Question D" should have number "2" on the edit quiz page
And "Question E" should have number "3" on the edit quiz page
# Delete from first question in the last remaining page. Are the page breaks updated?
When I delete "Question A" in the quiz by clicking the delete icon
Then "Question B" should have number "1" on the edit quiz page
And "Question C" should have number "i" on the edit quiz page
And "Question D" should have number "2" on the edit quiz page
And "Question E" should have number "3" on the edit quiz page
When I click on the "Add" page break icon after question "Question C"
Then I should see "Page 1"
And I should see "Question B" on quiz page "1"
And I should see "Question C" on quiz page "1"
Then I should see "Page 2"
And I should see "Question D" on quiz page "2"
And I should see "Question E" on quiz page "2"
# Test reorder of pages
When I click on the "Add" page break icon after question "Question B"
Then I should see "Page 1"
And I should see "Question B" on quiz page "1"
Then I should see "Page 2"
And I should see "Question C" on quiz page "2"
Then I should see "Page 3"
And I should see "Question D" on quiz page "3"
And I should see "Question E" on quiz page "3"

View File

@ -313,7 +313,6 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
this.groups = ['resource'];
this.samenodeclass = CSS.ACTIVITY;
this.parentnodeclass = CSS.SECTION;
//this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
this.samenodelabel = {
@ -467,12 +466,11 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
var responsetext = Y.JSON.parse(response.responseText);
var params = {element: dragnode, visible: responsetext.visible};
M.mod_quiz.quizbase.invoke_function('set_visibility_resource_ui', params);
Y.Moodle.mod_quiz.util.slot.reorder_slots();
this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
window.setTimeout(function() {
spinner.hide();
}, 250);
window.location.reload(true);
M.mod_quiz.resource_toolbox.reorganise_edit_page();
},
failure: function(tid, response) {
this.ajax_failure(response);
@ -556,7 +554,9 @@ M.mod_quiz.init_resource_dragdrop = function(params) {
"moodle-core-dragdrop",
"moodle-core-notification",
"moodle-mod_quiz-quizbase",
"moodle-mod_quiz-util",
"moodle-mod_quiz-util-base",
"moodle-mod_quiz-util-page",
"moodle-mod_quiz-util-slot",
"moodle-course-util"
]
});

File diff suppressed because one or more lines are too long

View File

@ -308,7 +308,6 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
this.groups = ['resource'];
this.samenodeclass = CSS.ACTIVITY;
this.parentnodeclass = CSS.SECTION;
//this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
this.samenodelabel = {
@ -462,12 +461,11 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
var responsetext = Y.JSON.parse(response.responseText);
var params = {element: dragnode, visible: responsetext.visible};
M.mod_quiz.quizbase.invoke_function('set_visibility_resource_ui', params);
Y.Moodle.mod_quiz.util.slot.reorder_slots();
this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
window.setTimeout(function() {
spinner.hide();
}, 250);
window.location.reload(true);
M.mod_quiz.resource_toolbox.reorganise_edit_page();
},
failure: function(tid, response) {
this.ajax_failure(response);
@ -551,7 +549,9 @@ M.mod_quiz.init_resource_dragdrop = function(params) {
"moodle-core-dragdrop",
"moodle-core-notification",
"moodle-mod_quiz-quizbase",
"moodle-mod_quiz-util",
"moodle-mod_quiz-util-base",
"moodle-mod_quiz-util-page",
"moodle-mod_quiz-util-slot",
"moodle-course-util"
]
});

View File

@ -79,9 +79,9 @@ M.mod_quiz.edit.swap_sections = function(Y, node1, node2) {
SECTIONADDMENUS : 'section_add_menus'
};
var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.mod_quiz.edit.get_section_selector(Y));
var sectionlist = Y.Node.all('.' + CSS.COURSECONTENT + ' ' + M.mod_quiz.edit.get_section_selector(Y));
// Swap menus.
sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
sectionlist.item(node1).one('.' + CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.' + CSS.SECTIONADDMENUS));
};
/**
@ -117,7 +117,7 @@ M.mod_quiz.edit.process_sections = function(Y, sectionlist, response, sectionfro
for (var i = sectionfrom; i <= sectionto; i++) {
// Update section title.
sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
sectionlist.item(i).one('.' + CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
// Update move icon.
ele = sectionlist.item(i).one(SELECTORS.SECTIONLEFTSIDE);

View File

@ -79,9 +79,9 @@ M.mod_quiz.edit.swap_sections = function(Y, node1, node2) {
SECTIONADDMENUS : 'section_add_menus'
};
var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.mod_quiz.edit.get_section_selector(Y));
var sectionlist = Y.Node.all('.' + CSS.COURSECONTENT + ' ' + M.mod_quiz.edit.get_section_selector(Y));
// Swap menus.
sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
sectionlist.item(node1).one('.' + CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.' + CSS.SECTIONADDMENUS));
};
/**
@ -117,7 +117,7 @@ M.mod_quiz.edit.process_sections = function(Y, sectionlist, response, sectionfro
for (var i = sectionfrom; i <= sectionto; i++) {
// Update section title.
sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
sectionlist.item(i).one('.' + CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
// Update move icon.
ele = sectionlist.item(i).one(SELECTORS.SECTIONLEFTSIDE);

View File

@ -311,10 +311,10 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// The user is deleting the activity.
this.delete_with_confirmation(ev, node, activity, action);
break;
case 'linkpage':
case 'unlinkpage':
// The user is linking or unlinking pages.
this.link_page(ev, node, activity, action);
case 'addpagebreak':
case 'removepagebreak':
// The user is adding or removing a page break.
this.update_page_break(ev, node, activity, action);
break;
default:
// Nothing to do here!
@ -350,10 +350,10 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @chainable
*/
delete_with_confirmation: function(ev, button, activity) {
// Prevent the default button action
// Prevent the default button action.
ev.preventDefault();
// Get the element we're working on
// Get the element we're working on.
var element = activity,
// Create confirm string (different if element has or does not have name)
confirmstring = '',
@ -370,19 +370,24 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// If it is confirmed.
confirm.on('complete-yes', function() {
// Actually remove the element.
element.remove();
Y.Moodle.mod_quiz.util.slot.reorder_slots();
var spinner = this.add_spinner(element);
var data = {
'class': 'resource',
'action': 'DELETE',
'id': Y.Moodle.mod_quiz.util.slot.getId(element)
};
this.send_request(data);
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
window.location.reload(true);
this.send_request(data, spinner, function(response) {
if (response.deleted) {
// Actually remove the element.
Y.Moodle.mod_quiz.util.slot.remove(element);
this.reorganise_edit_page();
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
} else {
window.location.reload(true);
}
});
}, this);
@ -425,12 +430,12 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
M.core.actionmenu.instance.hideMenu();
}
// Try to retrieve the existing string from the server
// Try to retrieve the existing string from the server.
if (response.instancemaxmark) {
maxmarktext = response.instancemaxmark;
}
// Create the editor and submit button
// Create the editor and submit button.
var editform = Y.Node.create('<form action="#" />');
var editinstructions = Y.Node.create('<span class="' + CSS.EDITINSTRUCTIONS + '" id="id_editinstructions" />')
.set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
@ -442,7 +447,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
'size' : parseInt(this.get('config').questiondecimalpoints, 10) + 2
});
// Clear the existing content and put the editor in
// Clear the existing content and put the editor in.
editform.appendChild(editor);
editform.setData('anchor', anchor);
instance.insert(editinstructions, 'before');
@ -457,7 +462,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// We hide various components whilst editing:
activity.addClass(CSS.EDITINGMAXMARK);
// Focus and select the editor text
// Focus and select the editor text.
editor.focus().select();
// Cancel the edit if we lose focus or the escape key is pressed.
@ -482,7 +487,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @param {String} originalmaxmark The original maxmark the activity or resource had.
*/
edit_maxmark_submit : function(ev, activity, originalmaxmark) {
// We don't actually want to submit anything
// We don't actually want to submit anything.
ev.preventDefault();
var newmaxmark = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYMAXMARK).get('value'));
var spinner = this.add_spinner(activity);
@ -560,97 +565,59 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* the other slots
*
* @protected
* @method link_page
* @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.
* @chainable
*/
link_page: function(ev, button, activity, action) {
update_page_break: function(ev, button, activity, action) {
// Prevent the default button action
ev.preventDefault();
activity = activity.next('li.activity.slot');
var spinner = this.add_spinner(activity),
nextactivity = activity.next('li.activity.slot');
var spinner = this.add_spinner(nextactivity),
slotid = 0;
var value = action === 'linkpage' ? 1:2;
var value = action === 'removepagebreak' ? 1 : 2;
var data = {
'class': 'resource',
'field': 'linkslottopage',
'field': 'updatepagebreak',
'id': slotid,
'value': value
};
slotid = Y.Moodle.mod_quiz.util.slot.getId(activity);
slotid = Y.Moodle.mod_quiz.util.slot.getId(nextactivity);
if (slotid) {
data.id = Number(slotid);
}
this.send_request(data, spinner, function(response) {
window.location.reload(true);
// if (response.slots) {
// this.repaginate_slots(response.slots);
// }
if (response.slots) {
if (action === 'addpagebreak') {
Y.Moodle.mod_quiz.util.page.add(activity);
} else {
var page = activity.next(Y.Moodle.mod_quiz.util.page.SELECTORS.PAGE);
Y.Moodle.mod_quiz.util.page.remove(page, true);
}
this.reorganise_edit_page();
} else {
window.location.reload(true);
}
});
return this;
},
repaginate_slots: function(slots) {
this.slots = slots;
var section = Y.one(SELECTOR.PAGECONTENT + ' ' + SELECTOR.SECTIONUL),
activities = section.all(SELECTOR.ACTIVITYLI);
activities.each(function(node) {
// What element is it? page/slot/link
// what is the current slot?
var type;
var slot;
if(node.hasClass(CSS.PAGE)){
type = this.NODE_PAGE;
slot = node.next(SELECTOR.SLOTLI);
} else if (node.hasClass(CSS.SLOT)){
type = this.NODE_SLOT;
slot = node;
} else if (node.hasClass(CSS.JOIN)){
type = this.NODE_JOIN;
slot = node.previous(SELECTOR.SLOTLI);
}
// getSlotnumber() Should be a method of util.slot
var slotnumber = Number(Y.Moodle.mod_quiz.util.slot.getNumber(slot));
if(!type){
// Nothing we can do.
return;
}
// Is it correct?
if(!this.slots.hasOwnProperty(slotnumber)){
// An error. We should handle this.
return;
}
var slotdata = this.slots[slotnumber];
if(type === this.NODE_PAGE){
// Get page number
var pagenumber = Y.Moodle.mod_quiz.util.page.getNumber(node);
// Is the page number correct?
if (slotdata.page === pagenumber) {
console.log('slotdata.page == pagenumber return');
return;
}
if (pagenumber < slotdata.page) {
// Remove page node.
node.remove();
}
else {
// Add page node.
console.log('pagenumber > slotdata.page update page number');
}
}
}, this);
/**
* Reorganise the UI after every edit action.
*
* @protected
* @method reorganise_edit_page
*/
reorganise_edit_page: function() {
Y.Moodle.mod_quiz.util.slot.reorderSlots();
Y.Moodle.mod_quiz.util.slot.reorderPageBreaks();
Y.Moodle.mod_quiz.util.page.reorderPages();
},
NAME : 'mod_quiz-resource-toolbox',

File diff suppressed because one or more lines are too long

View File

@ -311,10 +311,10 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// The user is deleting the activity.
this.delete_with_confirmation(ev, node, activity, action);
break;
case 'linkpage':
case 'unlinkpage':
// The user is linking or unlinking pages.
this.link_page(ev, node, activity, action);
case 'addpagebreak':
case 'removepagebreak':
// The user is adding or removing a page break.
this.update_page_break(ev, node, activity, action);
break;
default:
// Nothing to do here!
@ -350,10 +350,10 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @chainable
*/
delete_with_confirmation: function(ev, button, activity) {
// Prevent the default button action
// Prevent the default button action.
ev.preventDefault();
// Get the element we're working on
// Get the element we're working on.
var element = activity,
// Create confirm string (different if element has or does not have name)
confirmstring = '',
@ -370,19 +370,24 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// If it is confirmed.
confirm.on('complete-yes', function() {
// Actually remove the element.
element.remove();
Y.Moodle.mod_quiz.util.slot.reorder_slots();
var spinner = this.add_spinner(element);
var data = {
'class': 'resource',
'action': 'DELETE',
'id': Y.Moodle.mod_quiz.util.slot.getId(element)
};
this.send_request(data);
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
window.location.reload(true);
this.send_request(data, spinner, function(response) {
if (response.deleted) {
// Actually remove the element.
Y.Moodle.mod_quiz.util.slot.remove(element);
this.reorganise_edit_page();
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
} else {
window.location.reload(true);
}
});
}, this);
@ -425,12 +430,12 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
M.core.actionmenu.instance.hideMenu();
}
// Try to retrieve the existing string from the server
// Try to retrieve the existing string from the server.
if (response.instancemaxmark) {
maxmarktext = response.instancemaxmark;
}
// Create the editor and submit button
// Create the editor and submit button.
var editform = Y.Node.create('<form action="#" />');
var editinstructions = Y.Node.create('<span class="' + CSS.EDITINSTRUCTIONS + '" id="id_editinstructions" />')
.set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
@ -442,7 +447,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
'size' : parseInt(this.get('config').questiondecimalpoints, 10) + 2
});
// Clear the existing content and put the editor in
// Clear the existing content and put the editor in.
editform.appendChild(editor);
editform.setData('anchor', anchor);
instance.insert(editinstructions, 'before');
@ -457,7 +462,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// We hide various components whilst editing:
activity.addClass(CSS.EDITINGMAXMARK);
// Focus and select the editor text
// Focus and select the editor text.
editor.focus().select();
// Cancel the edit if we lose focus or the escape key is pressed.
@ -482,7 +487,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @param {String} originalmaxmark The original maxmark the activity or resource had.
*/
edit_maxmark_submit : function(ev, activity, originalmaxmark) {
// We don't actually want to submit anything
// We don't actually want to submit anything.
ev.preventDefault();
var newmaxmark = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYMAXMARK).get('value'));
var spinner = this.add_spinner(activity);
@ -560,97 +565,59 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* the other slots
*
* @protected
* @method link_page
* @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.
* @chainable
*/
link_page: function(ev, button, activity, action) {
update_page_break: function(ev, button, activity, action) {
// Prevent the default button action
ev.preventDefault();
activity = activity.next('li.activity.slot');
var spinner = this.add_spinner(activity),
nextactivity = activity.next('li.activity.slot');
var spinner = this.add_spinner(nextactivity),
slotid = 0;
var value = action === 'linkpage' ? 1:2;
var value = action === 'removepagebreak' ? 1 : 2;
var data = {
'class': 'resource',
'field': 'linkslottopage',
'field': 'updatepagebreak',
'id': slotid,
'value': value
};
slotid = Y.Moodle.mod_quiz.util.slot.getId(activity);
slotid = Y.Moodle.mod_quiz.util.slot.getId(nextactivity);
if (slotid) {
data.id = Number(slotid);
}
this.send_request(data, spinner, function(response) {
window.location.reload(true);
// if (response.slots) {
// this.repaginate_slots(response.slots);
// }
if (response.slots) {
if (action === 'addpagebreak') {
Y.Moodle.mod_quiz.util.page.add(activity);
} else {
var page = activity.next(Y.Moodle.mod_quiz.util.page.SELECTORS.PAGE);
Y.Moodle.mod_quiz.util.page.remove(page, true);
}
this.reorganise_edit_page();
} else {
window.location.reload(true);
}
});
return this;
},
repaginate_slots: function(slots) {
this.slots = slots;
var section = Y.one(SELECTOR.PAGECONTENT + ' ' + SELECTOR.SECTIONUL),
activities = section.all(SELECTOR.ACTIVITYLI);
activities.each(function(node) {
// What element is it? page/slot/link
// what is the current slot?
var type;
var slot;
if(node.hasClass(CSS.PAGE)){
type = this.NODE_PAGE;
slot = node.next(SELECTOR.SLOTLI);
} else if (node.hasClass(CSS.SLOT)){
type = this.NODE_SLOT;
slot = node;
} else if (node.hasClass(CSS.JOIN)){
type = this.NODE_JOIN;
slot = node.previous(SELECTOR.SLOTLI);
}
// getSlotnumber() Should be a method of util.slot
var slotnumber = Number(Y.Moodle.mod_quiz.util.slot.getNumber(slot));
if(!type){
// Nothing we can do.
return;
}
// Is it correct?
if(!this.slots.hasOwnProperty(slotnumber)){
// An error. We should handle this.
return;
}
var slotdata = this.slots[slotnumber];
if(type === this.NODE_PAGE){
// Get page number
var pagenumber = Y.Moodle.mod_quiz.util.page.getNumber(node);
// Is the page number correct?
if (slotdata.page === pagenumber) {
console.log('slotdata.page == pagenumber return');
return;
}
if (pagenumber < slotdata.page) {
// Remove page node.
node.remove();
}
else {
// Add page node.
console.log('pagenumber > slotdata.page update page number');
}
}
}, this);
/**
* Reorganise the UI after every edit action.
*
* @protected
* @method reorganise_edit_page
*/
reorganise_edit_page: function() {
Y.Moodle.mod_quiz.util.slot.reorderSlots();
Y.Moodle.mod_quiz.util.slot.reorderPageBreaks();
Y.Moodle.mod_quiz.util.page.reorderPages();
},
NAME : 'mod_quiz-resource-toolbox',

View File

@ -16,13 +16,23 @@ Y.namespace('Moodle.mod_quiz.util.page');
* @static
*/
Y.Moodle.mod_quiz.util.page = {
CSS: {
PAGE : 'page'
},
CONSTANTS: {
PAGEIDPREFIX : 'page-',
PAGENUMBERPREFIX : 'Page '
ACTIONMENUIDPREFIX: 'action-menu-',
ACTIONMENUBARIDSUFFIX: '-menubar',
ACTIONMENUMENUIDSUFFIX: '-menu',
PAGEIDPREFIX: 'page-',
PAGENUMBERPREFIX: M.util.get_string('page', 'moodle') + ' '
},
SELECTORS: {
ACTIONMENU: 'div.moodle-actionmenu',
ACTIONMENUBAR: 'ul.menubar',
ACTIONMENUMENU: 'ul.menu',
PAGE: 'li.page',
INSTANCENAME: '.instancename'
INSTANCENAME: '.instancename',
NUMBER: 'span.text'
},
/**
@ -37,7 +47,18 @@ Y.Moodle.mod_quiz.util.page = {
},
/**
* Determines the page ID for the provided page.
* Retrieve the page item from one of it's previous siblings.
*
* @method getPageFromSlot
* @param pagecomponent {Node} The component Node.
* @return {Node|null} The Page Node.
*/
getPageFromSlot: function(slot) {
return Y.one(slot).previous(this.SELECTORS.PAGE);
},
/**
* Returns the page ID for the provided page.
*
* @method getId
* @param page {Node} The page to find an ID for.
@ -56,6 +77,18 @@ Y.Moodle.mod_quiz.util.page = {
return false;
},
/**
* Updates the page id for the provided page.
*
* @method setId
* @param page {Node} The page to update the number for.
* @param id int The id value.
* @return void
*/
setId: function(page, id) {
page.set('id', this.CONSTANTS.PAGEIDPREFIX + id);
},
/**
* Determines the page name for the provided page.
*
@ -79,8 +112,8 @@ Y.Moodle.mod_quiz.util.page = {
* @return {Number|false} The number of the page in question or false if no number was found.
*/
getNumber: function(page) {
// We perform a simple substitution operation to get the ID.
var number = page.get('text').replace(
// We perform a simple substitution operation to get the number.
var number = page.one(this.SELECTORS.NUMBER).get('text').replace(
this.CONSTANTS.PAGENUMBERPREFIX, '');
// Attempt to validate the ID.
@ -89,6 +122,206 @@ Y.Moodle.mod_quiz.util.page = {
return number;
}
return false;
},
/**
* Updates the page number for the provided page.
*
* @method setNumber
* @param page {Node} The page to update the number for.
* @return void
*/
setNumber: function(page, number) {
page.one(this.SELECTORS.NUMBER).set('text', this.CONSTANTS.PAGENUMBERPREFIX + number);
},
/**
* Returns a list of all page elements.
*
* @method getPages
* @return {node[]} An array containing page nodes.
*/
getPages: function() {
return Y.all(Y.Moodle.mod_quiz.util.slot.SELECTORS.PAGECONTENT + ' ' + Y.Moodle.mod_quiz.util.slot.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.PAGE);
},
/**
* Is the given element a page element?
*
* @method isPage
* @param page Page node
* @return boolean
*/
isPage: function(page) {
if (!page) {
return false;
}
return page.hasClass(this.CSS.PAGE);
},
/**
* Does the page have atleast one slot?
*
* @method isEmpty
* @param page Page node
* @return boolean
*/
isEmpty: function(page) {
var activity = page.next('li.activity');
if (!activity) {
return true;
}
return !activity.hasClass('slot');
},
/**
* Add a page and related elements to the list of slots.
*
* @method add
* @param beforenode Int | Node | HTMLElement | String to add
* @return page Page node
*/
add: function(beforenode) {
var pagenumber = this.getNumber(this.getPageFromSlot(beforenode)) + 1;
var pagehtml = M.mod_quiz.resource_toolbox.get('config').pagehtml;
// Normalise the page number.
pagehtml = pagehtml.replace(/%%PAGENUMBER%%/g, pagenumber);
// Create the page node.
var page = Y.Node.create(pagehtml);
// Assign is as a drop target.
YUI().use('dd-drop', function(Y) {
var drop = new Y.DD.Drop({
node: page,
groups: M.mod_quiz.dragres.groups
});
page.drop = drop;
});
// Insert in the correct place.
beforenode.insert(page, 'after');
// Enhance the add menu to make if fully visible and clickable.
M.core.actionmenu.newDOMNode(page);
return page;
},
/**
* Remove a page and related elements from the list of slots.
*
* @method remove
* @param page Page node
* @return void
*/
remove: function(page, keeppagebreak) {
// Remove page break from previous slot.
var previousslot = page.previous(Y.Moodle.mod_quiz.util.slot.SELECTORS.SLOT);
if (!keeppagebreak && previousslot) {
Y.Moodle.mod_quiz.util.slot.removePageBreak(previousslot);
}
page.remove();
},
/**
* Reset the order of the numbers given to each page.
*
* @method reorderPages
* @return void
*/
reorderPages: function() {
// Get list of page nodes.
var pages = this.getPages(), currentpagenumber = 0;
// Loop through pages incrementing the number each time.
pages.each(function(page) {
// Is the page empty?
if (this.isEmpty(page)) {
var keeppagebreak = page.next('li.slot') ? true : false;
this.remove(page, keeppagebreak);
return;
}
currentpagenumber++;
// Set page number.
this.setNumber(page, currentpagenumber);
this.setId(page, currentpagenumber);
}, this);
// Reorder action menus
this.reorderActionMenus();
},
/**
* Reset the order of the numbers given to each action menu.
*
* @method reorderActionMenus
* @return void
*/
reorderActionMenus: function() {
// Get list of action menu nodes.
var actionmenus = this.getActionMenus();
// Loop through pages incrementing the number each time.
actionmenus.each(function(actionmenu, key) {
var previousActionMenu = actionmenus.item(key - 1);
previousActionMenunumber = 0;
if (previousActionMenu) {
previousActionMenunumber = this.getActionMenuId(previousActionMenu);
}
var id = previousActionMenunumber + 1;
// Set menu id.
this.setActionMenuId(actionmenu, id);
// Update action-menu-1-menubar
var menubar = actionmenu.one(this.SELECTORS.ACTIONMENUBAR);
menubar.set('id', this.CONSTANTS.ACTIONMENUIDPREFIX + id + this.CONSTANTS.ACTIONMENUBARIDSUFFIX);
// Update action-menu-1-menu
var menumenu = actionmenu.one(this.SELECTORS.ACTIONMENUMENU);
menumenu.set('id', this.CONSTANTS.ACTIONMENUIDPREFIX + id + this.CONSTANTS.ACTIONMENUMENUIDSUFFIX);
}, this);
},
/**
* Returns a list of all page elements.
*
* @method getActionMenus
* @return {node[]} An array containing page nodes.
*/
getActionMenus: function() {
return Y.all(Y.Moodle.mod_quiz.util.slot.SELECTORS.PAGECONTENT + ' ' + Y.Moodle.mod_quiz.util.slot.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.ACTIONMENU);
},
/**
* Returns the ID for the provided action menu.
*
* @method getId
* @param actionmenu {Node} The actionmenu to find an ID for.
* @return {Number|false} The ID of the actionmenu in question or false if no ID was found.
*/
getActionMenuId: function(actionmenu) {
// We perform a simple substitution operation to get the ID.
var id = actionmenu.get('id').replace(
this.CONSTANTS.ACTIONMENUIDPREFIX, '');
// Attempt to validate the ID.
id = parseInt(id, 10);
if (typeof id === 'number' && isFinite(id)) {
return id;
}
return false;
},
/**
* Updates the page id for the provided page.
*
* @method setId
* @param page {Node} The page to update the number for.
* @param id int The id value.
* @return void
*/
setActionMenuId: function(actionmenu, id) {
actionmenu.set('id', this.CONSTANTS.ACTIONMENUIDPREFIX + id);
}
};

View File

@ -1 +1 @@
YUI.add("moodle-mod_quiz-util-page",function(e,t){e.namespace("Moodle.mod_quiz.util.page"),e.Moodle.mod_quiz.util.page={CONSTANTS:{PAGEIDPREFIX:"page-",PAGENUMBERPREFIX:"Page "},SELECTORS:{PAGE:"li.page",INSTANCENAME:".instancename"},getPageFromComponent:function(t){return e.one(t).ancestor(this.SELECTORS.PAGE,!0)},getId:function(e){var t=e.get("id").replace(this.CONSTANTS.PAGEIDPREFIX,"");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){var t=e.get("text").replace(this.CONSTANTS.PAGENUMBERPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1}}},"@VERSION@",{requires:["node","moodle-mod_quiz-util-base"]});
YUI.add("moodle-mod_quiz-util-page",function(e,t){e.namespace("Moodle.mod_quiz.util.page"),e.Moodle.mod_quiz.util.page={CSS:{PAGE:"page"},CONSTANTS:{ACTIONMENUIDPREFIX:"action-menu-",ACTIONMENUBARIDSUFFIX:"-menubar",ACTIONMENUMENUIDSUFFIX:"-menu",PAGEIDPREFIX:"page-",PAGENUMBERPREFIX:M.util.get_string("page","moodle")+" "},SELECTORS:{ACTIONMENU:"div.moodle-actionmenu",ACTIONMENUBAR:"ul.menubar",ACTIONMENUMENU:"ul.menu",PAGE:"li.page",INSTANCENAME:".instancename",NUMBER:"span.text"},getPageFromComponent:function(t){return e.one(t).ancestor(this.SELECTORS.PAGE,!0)},getPageFromSlot:function(t){return e.one(t).previous(this.SELECTORS.PAGE)},getId:function(e){var t=e.get("id").replace(this.CONSTANTS.PAGEIDPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},setId:function(e,t){e.set("id",this.CONSTANTS.PAGEIDPREFIX+t)},getName:function(e){var t=e.one(this.SELECTORS.INSTANCENAME);return t?t.get("firstChild").get("data"):null},getNumber:function(e){var t=e.one(this.SELECTORS.NUMBER).get("text").replace(this.CONSTANTS.PAGENUMBERPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},setNumber:function(e,t){e.one(this.SELECTORS.NUMBER).set("text",this.CONSTANTS.PAGENUMBERPREFIX+t)},getPages:function(){return e.all(e.Moodle.mod_quiz.util.slot.SELECTORS.PAGECONTENT+" "+e.Moodle.mod_quiz.util.slot.SELECTORS.SECTIONUL+" "+this.SELECTORS.PAGE)},isPage:function(e){return e?e.hasClass(this.CSS.PAGE):!1},isEmpty:function(e){var t=e.next("li.activity");return t?!t.hasClass("slot"):!0},add:function(t){var n=this.getNumber(this.getPageFromSlot(t))+1,r=M.mod_quiz.resource_toolbox.get("config").pagehtml;r=r.replace(/%%PAGENUMBER%%/g,n);var i=e.Node.create(r);return YUI().use("dd-drop",function(e){var t=new e.DD.Drop({node:i,groups:M.mod_quiz.dragres.groups});i.drop=t}),t.insert(i,"after"),M.core.actionmenu.newDOMNode(i),i},remove:function(t,n){var r=t.previous(e.Moodle.mod_quiz.util.slot.SELECTORS.SLOT);!n&&r&&e.Moodle.mod_quiz.util.slot.removePageBreak(r),t.remove()},reorderPages:function(){var e=this.getPages(),t=0;e.each(function(e){if(this.isEmpty(e)){var n=e.next("li.slot")?!0:!1;this.remove(e,n);return}t++,this.setNumber(e,t),this.setId(e,t)},this),this.reorderActionMenus()},reorderActionMenus:function(){var e=this.getActionMenus();e.each(function(t,n){var r=e.item(n-1);previousActionMenunumber=0,r&&(previousActionMenunumber=this.getActionMenuId(r));var i=previousActionMenunumber+1;this.setActionMenuId(t,i);var s=t.one(this.SELECTORS.ACTIONMENUBAR);s.set("id",this.CONSTANTS.ACTIONMENUIDPREFIX+i+this.CONSTANTS.ACTIONMENUBARIDSUFFIX);var o=t.one(this.SELECTORS.ACTIONMENUMENU);o.set("id",this.CONSTANTS.ACTIONMENUIDPREFIX+i+this.CONSTANTS.ACTIONMENUMENUIDSUFFIX)},this)},getActionMenus:function(){return e.all(e.Moodle.mod_quiz.util.slot.SELECTORS.PAGECONTENT+" "+e.Moodle.mod_quiz.util.slot.SELECTORS.SECTIONUL+" "+this.SELECTORS.ACTIONMENU)},getActionMenuId:function(e){var t=e.get("id").replace(this.CONSTANTS.ACTIONMENUIDPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},setActionMenuId:function(e,t){e.set("id",this.CONSTANTS.ACTIONMENUIDPREFIX+t)}}},"@VERSION@",{requires:["node","moodle-mod_quiz-util-base"]});

View File

@ -16,13 +16,23 @@ Y.namespace('Moodle.mod_quiz.util.page');
* @static
*/
Y.Moodle.mod_quiz.util.page = {
CSS: {
PAGE : 'page'
},
CONSTANTS: {
PAGEIDPREFIX : 'page-',
PAGENUMBERPREFIX : 'Page '
ACTIONMENUIDPREFIX: 'action-menu-',
ACTIONMENUBARIDSUFFIX: '-menubar',
ACTIONMENUMENUIDSUFFIX: '-menu',
PAGEIDPREFIX: 'page-',
PAGENUMBERPREFIX: M.util.get_string('page', 'moodle') + ' '
},
SELECTORS: {
ACTIONMENU: 'div.moodle-actionmenu',
ACTIONMENUBAR: 'ul.menubar',
ACTIONMENUMENU: 'ul.menu',
PAGE: 'li.page',
INSTANCENAME: '.instancename'
INSTANCENAME: '.instancename',
NUMBER: 'span.text'
},
/**
@ -37,7 +47,18 @@ Y.Moodle.mod_quiz.util.page = {
},
/**
* Determines the page ID for the provided page.
* Retrieve the page item from one of it's previous siblings.
*
* @method getPageFromSlot
* @param pagecomponent {Node} The component Node.
* @return {Node|null} The Page Node.
*/
getPageFromSlot: function(slot) {
return Y.one(slot).previous(this.SELECTORS.PAGE);
},
/**
* Returns the page ID for the provided page.
*
* @method getId
* @param page {Node} The page to find an ID for.
@ -56,6 +77,18 @@ Y.Moodle.mod_quiz.util.page = {
return false;
},
/**
* Updates the page id for the provided page.
*
* @method setId
* @param page {Node} The page to update the number for.
* @param id int The id value.
* @return void
*/
setId: function(page, id) {
page.set('id', this.CONSTANTS.PAGEIDPREFIX + id);
},
/**
* Determines the page name for the provided page.
*
@ -79,8 +112,8 @@ Y.Moodle.mod_quiz.util.page = {
* @return {Number|false} The number of the page in question or false if no number was found.
*/
getNumber: function(page) {
// We perform a simple substitution operation to get the ID.
var number = page.get('text').replace(
// We perform a simple substitution operation to get the number.
var number = page.one(this.SELECTORS.NUMBER).get('text').replace(
this.CONSTANTS.PAGENUMBERPREFIX, '');
// Attempt to validate the ID.
@ -89,6 +122,206 @@ Y.Moodle.mod_quiz.util.page = {
return number;
}
return false;
},
/**
* Updates the page number for the provided page.
*
* @method setNumber
* @param page {Node} The page to update the number for.
* @return void
*/
setNumber: function(page, number) {
page.one(this.SELECTORS.NUMBER).set('text', this.CONSTANTS.PAGENUMBERPREFIX + number);
},
/**
* Returns a list of all page elements.
*
* @method getPages
* @return {node[]} An array containing page nodes.
*/
getPages: function() {
return Y.all(Y.Moodle.mod_quiz.util.slot.SELECTORS.PAGECONTENT + ' ' + Y.Moodle.mod_quiz.util.slot.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.PAGE);
},
/**
* Is the given element a page element?
*
* @method isPage
* @param page Page node
* @return boolean
*/
isPage: function(page) {
if (!page) {
return false;
}
return page.hasClass(this.CSS.PAGE);
},
/**
* Does the page have atleast one slot?
*
* @method isEmpty
* @param page Page node
* @return boolean
*/
isEmpty: function(page) {
var activity = page.next('li.activity');
if (!activity) {
return true;
}
return !activity.hasClass('slot');
},
/**
* Add a page and related elements to the list of slots.
*
* @method add
* @param beforenode Int | Node | HTMLElement | String to add
* @return page Page node
*/
add: function(beforenode) {
var pagenumber = this.getNumber(this.getPageFromSlot(beforenode)) + 1;
var pagehtml = M.mod_quiz.resource_toolbox.get('config').pagehtml;
// Normalise the page number.
pagehtml = pagehtml.replace(/%%PAGENUMBER%%/g, pagenumber);
// Create the page node.
var page = Y.Node.create(pagehtml);
// Assign is as a drop target.
YUI().use('dd-drop', function(Y) {
var drop = new Y.DD.Drop({
node: page,
groups: M.mod_quiz.dragres.groups
});
page.drop = drop;
});
// Insert in the correct place.
beforenode.insert(page, 'after');
// Enhance the add menu to make if fully visible and clickable.
M.core.actionmenu.newDOMNode(page);
return page;
},
/**
* Remove a page and related elements from the list of slots.
*
* @method remove
* @param page Page node
* @return void
*/
remove: function(page, keeppagebreak) {
// Remove page break from previous slot.
var previousslot = page.previous(Y.Moodle.mod_quiz.util.slot.SELECTORS.SLOT);
if (!keeppagebreak && previousslot) {
Y.Moodle.mod_quiz.util.slot.removePageBreak(previousslot);
}
page.remove();
},
/**
* Reset the order of the numbers given to each page.
*
* @method reorderPages
* @return void
*/
reorderPages: function() {
// Get list of page nodes.
var pages = this.getPages(), currentpagenumber = 0;
// Loop through pages incrementing the number each time.
pages.each(function(page) {
// Is the page empty?
if (this.isEmpty(page)) {
var keeppagebreak = page.next('li.slot') ? true : false;
this.remove(page, keeppagebreak);
return;
}
currentpagenumber++;
// Set page number.
this.setNumber(page, currentpagenumber);
this.setId(page, currentpagenumber);
}, this);
// Reorder action menus
this.reorderActionMenus();
},
/**
* Reset the order of the numbers given to each action menu.
*
* @method reorderActionMenus
* @return void
*/
reorderActionMenus: function() {
// Get list of action menu nodes.
var actionmenus = this.getActionMenus();
// Loop through pages incrementing the number each time.
actionmenus.each(function(actionmenu, key) {
var previousActionMenu = actionmenus.item(key - 1);
previousActionMenunumber = 0;
if (previousActionMenu) {
previousActionMenunumber = this.getActionMenuId(previousActionMenu);
}
var id = previousActionMenunumber + 1;
// Set menu id.
this.setActionMenuId(actionmenu, id);
// Update action-menu-1-menubar
var menubar = actionmenu.one(this.SELECTORS.ACTIONMENUBAR);
menubar.set('id', this.CONSTANTS.ACTIONMENUIDPREFIX + id + this.CONSTANTS.ACTIONMENUBARIDSUFFIX);
// Update action-menu-1-menu
var menumenu = actionmenu.one(this.SELECTORS.ACTIONMENUMENU);
menumenu.set('id', this.CONSTANTS.ACTIONMENUIDPREFIX + id + this.CONSTANTS.ACTIONMENUMENUIDSUFFIX);
}, this);
},
/**
* Returns a list of all page elements.
*
* @method getActionMenus
* @return {node[]} An array containing page nodes.
*/
getActionMenus: function() {
return Y.all(Y.Moodle.mod_quiz.util.slot.SELECTORS.PAGECONTENT + ' ' + Y.Moodle.mod_quiz.util.slot.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.ACTIONMENU);
},
/**
* Returns the ID for the provided action menu.
*
* @method getId
* @param actionmenu {Node} The actionmenu to find an ID for.
* @return {Number|false} The ID of the actionmenu in question or false if no ID was found.
*/
getActionMenuId: function(actionmenu) {
// We perform a simple substitution operation to get the ID.
var id = actionmenu.get('id').replace(
this.CONSTANTS.ACTIONMENUIDPREFIX, '');
// Attempt to validate the ID.
id = parseInt(id, 10);
if (typeof id === 'number' && isFinite(id)) {
return id;
}
return false;
},
/**
* Updates the page id for the provided page.
*
* @method setId
* @param page {Node} The page to update the number for.
* @param id int The id value.
* @return void
*/
setActionMenuId: function(actionmenu, id) {
actionmenu.set('id', this.CONSTANTS.ACTIONMENUIDPREFIX + id);
}
};

View File

@ -16,14 +16,22 @@ Y.namespace('Moodle.mod_quiz.util.slot');
* @static
*/
Y.Moodle.mod_quiz.util.slot = {
CSS: {
SLOT : 'slot',
QUESTIONTYPEDESCRIPTION : 'qtype_description'
},
CONSTANTS: {
SLOTIDPREFIX : 'slot-'
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'
},
@ -81,7 +89,12 @@ Y.Moodle.mod_quiz.util.slot = {
* @return {Number|false} The number of the slot in question or false if no number was found.
*/
getNumber: function(slot) {
var number = slot.one(this.SELECTORS.NUMBER).get('text');
if (!slot) {
return false;
}
// We perform a simple substitution operation to get the number.
var number = slot.one(this.SELECTORS.NUMBER).get('text').replace(
this.CONSTANTS.QUESTION, '');
// Attempt to validate the ID.
number = parseInt(number, 10);
if (typeof number === 'number' && isFinite(number)) {
@ -98,7 +111,8 @@ Y.Moodle.mod_quiz.util.slot = {
* @return void
*/
setNumber: function(slot, number) {
slot.one(this.SELECTORS.NUMBER).set('text', number);
var numbernode = slot.one(this.SELECTORS.NUMBER);
numbernode.setHTML('<span class="accesshide">' + this.CONSTANTS.QUESTION + '</span> ' + number);
},
/**
@ -112,7 +126,19 @@ Y.Moodle.mod_quiz.util.slot = {
},
/**
* Returns the previous slot to the give slot.
* Returns a list of all slot elements on the page that have numbers. Excudes description questions.
*
* @method getSlots
* @return {node[]} An array containing slot nodes.
*/
getNumberedSlots: function() {
var selector = this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL;
selector += ' ' + this.SELECTORS.SLOT + ':not(' + this.SELECTORS.QUESTIONTYPEDESCRIPTION + ')';
return Y.all(selector);
},
/**
* Returns the previous slot to the given slot.
*
* @method getPrevious
* @param slot Slot node
@ -122,27 +148,198 @@ Y.Moodle.mod_quiz.util.slot = {
return slot.previous(this.SELECTORS.SLOT);
},
/**
* Returns the previous numbered slot to the given slot.
*
* Ignores slots containing description question types.
*
* @method getPrevious
* @param slot Slot node
* @return {node|false} The previous slot node or false.
*/
getPreviousNumbered: function(slot) {
return slot.previous(this.SELECTORS.SLOT + ':not(' + this.SELECTORS.QUESTIONTYPEDESCRIPTION + ')');
},
/**
* Reset the order of the numbers given to each slot.
*
* @method reorder_slots
* @method reorderSlots
* @return void
*/
reorder_slots: function() {
reorderSlots: function() {
// Get list of slot nodes.
var slots = this.getSlots();
// Loop through slots incrementing the number each time.
slots.each(function(slot) {
var previousSlot = this.getPrevious(slot),
previousslotnumber = 0;
if(previousSlot){
if (!Y.Moodle.mod_quiz.util.page.getPageFromSlot(slot)) {
// Move the next page to the front.
var nextpage = slot.next(Y.Moodle.mod_quiz.util.page.SELECTORS.PAGE);
slot.swap(nextpage);
}
var previousSlot = this.getPreviousNumbered(slot);
previousslotnumber = 0;
if (slot.hasClass(this.CSS.QUESTIONTYPEDESCRIPTION)) {
return;
}
if (previousSlot) {
previousslotnumber = this.getNumber(previousSlot);
}
// Set slot number.
this.setNumber(slot, previousslotnumber + 1);
}, this);
},
/**
* Remove a slot and related elements from the list of slots.
*
* @method remove
* @param slot Slot node
* @return void
*/
remove: function(slot) {
var page = Y.Moodle.mod_quiz.util.page.getPageFromSlot(slot);
slot.remove();
// Is the page empty.
if (!Y.Moodle.mod_quiz.util.page.isEmpty(page)) {
return;
}
// If so remove it. Including add menu and page break.
Y.Moodle.mod_quiz.util.page.remove(page);
},
/**
* Returns a list of all page break elements on the page.
*
* @method getPageBreaks
* @return {node[]} An array containing page break nodes.
*/
getPageBreaks: function() {
var selector = this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL;
selector += ' ' + this.SELECTORS.SLOT + this.SELECTORS.PAGEBREAK;
return Y.all(selector);
},
/**
* Retrieve the page break element item from the given slot.
*
* @method getPageBreak
* @param slot Slot node
* @return {Node|null} The Page Break Node.
*/
getPageBreak: function(slot) {
return Y.one(slot).one(this.SELECTORS.PAGEBREAK);
},
/**
* Add a page break and related elements to the list of slots.
*
* @method addPageBreak
* @param beforenode Int | Node | HTMLElement | String to add
* @return pagebreak PageBreak node
*/
addPageBreak: function(slot) {
var nodetext = M.mod_quiz.resource_toolbox.get('config').addpageiconhtml;
nodetext = nodetext.replace('%%SLOT%%', this.getNumber(slot));
var pagebreak = Y.Node.create(nodetext);
slot.one('div').insert(pagebreak, 'after');
return pagebreak;
},
/**
* Remove a pagebreak from the given slot.
*
* @method removePageBreak
* @param slot Slot node
* @return boolean
*/
removePageBreak: function(slot) {
var pagebreak = this.getPageBreak(slot);
if (!pagebreak) {
return false;
}
pagebreak.remove();
return true;
},
/**
* Reorder each pagebreak by iterating through each related slot.
*
* @method reorderPageBreaks
* @return void
*/
reorderPageBreaks: function() {
// Get list of slot nodes.
var slots = this.getSlots(), slotnumber = 0;
// Loop through slots incrementing the number each time.
slots.each (function(slot, key) {
slotnumber++;
var pagebreak = this.getPageBreak(slot);
// Last slot won't have a page break.
if (!pagebreak && key === slots.size() - 1) {
return;
}
// No pagebreak and not last slot. Add one.
if (!pagebreak && key !== slots.size() - 1) {
pagebreak = this.addPageBreak(slot);
}
// Remove last page break if there is one.
if (pagebreak && key === slots.size() - 1) {
this.removePageBreak(slot);
}
// Get page break anchor element.
var pagebreaklink = pagebreak.get('childNodes').item(0);
// Get the correct title.
var nextactivity = slot.next('li.activity');
var titlename = '', action = '', uri = M.cfg.wwwroot;
var iconsrc = uri + '/theme/image.php?theme=clean&component=core';
// IE8 can't handle svg images.
if (Y.one('body.ie8')) {
iconsrc += '&svg=e%2F0';
}
if (Y.Moodle.mod_quiz.util.page.isPage(nextactivity)) {
action = titlename = 'removepagebreak';
iconsrc += '&image=e%2Fremove_page_break';
} else {
action = titlename = 'addpagebreak';
iconsrc += '&image=e%2Finsert_page_break';
}
var title = M.util.get_string(titlename, 'quiz');
// Update the link and image titles
pagebreaklink.set('title', title);
pagebreaklink.setData('action', action);
// Update the image title.
var icon = pagebreaklink.one(this.SELECTORS.ICON);
icon.set('title', title);
// Update the image src.
icon.set('src', iconsrc);
// Get anchor url parameters as an associative array.
var params = Y.QueryString.parse(pagebreaklink.get('href'));
// Update slot number.
params.slot = slotnumber;
// Create the new url.
var newurl = '';
for (var index in params) {
if (newurl.length) {
newurl += "&";
}
newurl += index + "=" + params[index];
}
// Update the anchor.
pagebreaklink.set('href', newurl);
}, this);
}
};

View File

@ -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={CONSTANTS:{SLOTIDPREFIX:"slot-"},SELECTORS:{SLOT:"li.slot",INSTANCENAME:".instancename",NUMBER:"span.slotnumber",PAGECONTENT:"div#page-content",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){var t=e.one(this.SELECTORS.NUMBER).get("text");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},setNumber:function(e,t){e.one(this.SELECTORS.NUMBER).set("text",t)},getSlots:function(){return e.all(this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL+" "+this.SELECTORS.SLOT)},getPrevious:function(e){return e.previous(this.SELECTORS.SLOT)},reorder_slots:function(){var e=this.getSlots();e.each(function(e){var t=this.getPrevious(e),n=0;t&&(n=this.getNumber(t)),this.setNumber(e,n+1)},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"},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);previousslotnumber=0;if(t.hasClass(this.CSS.QUESTIONTYPEDESCRIPTION))return;r&&(previousslotnumber=this.getNumber(r)),this.setNumber(t,previousslotnumber+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=r.next("li.activity"),a="",f="",l=M.cfg.wwwroot,c=l+"/theme/image.php?theme=clean&component=core";e.one("body.ie8")&&(c+="&svg=e%2F0"),e.Moodle.mod_quiz.util.page.isPage(u)?(f=a="removepagebreak",c+="&image=e%2Fremove_page_break"):(f=a="addpagebreak",c+="&image=e%2Finsert_page_break");var h=M.util.get_string(a,"quiz");o.set("title",h),o.setData("action",f);var p=o.one(this.SELECTORS.ICON);p.set("title",h),p.set("src",c);var d=e.QueryString.parse(o.get("href"));d.slot=n;var v="";for(var m in d)v.length&&(v+="&"),v+=m+"="+d[m];o.set("href",v)},this)}}},"@VERSION@",{requires:["node","moodle-mod_quiz-util-base"]});

View File

@ -16,14 +16,22 @@ Y.namespace('Moodle.mod_quiz.util.slot');
* @static
*/
Y.Moodle.mod_quiz.util.slot = {
CSS: {
SLOT : 'slot',
QUESTIONTYPEDESCRIPTION : 'qtype_description'
},
CONSTANTS: {
SLOTIDPREFIX : 'slot-'
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'
},
@ -81,7 +89,12 @@ Y.Moodle.mod_quiz.util.slot = {
* @return {Number|false} The number of the slot in question or false if no number was found.
*/
getNumber: function(slot) {
var number = slot.one(this.SELECTORS.NUMBER).get('text');
if (!slot) {
return false;
}
// We perform a simple substitution operation to get the number.
var number = slot.one(this.SELECTORS.NUMBER).get('text').replace(
this.CONSTANTS.QUESTION, '');
// Attempt to validate the ID.
number = parseInt(number, 10);
if (typeof number === 'number' && isFinite(number)) {
@ -98,7 +111,8 @@ Y.Moodle.mod_quiz.util.slot = {
* @return void
*/
setNumber: function(slot, number) {
slot.one(this.SELECTORS.NUMBER).set('text', number);
var numbernode = slot.one(this.SELECTORS.NUMBER);
numbernode.setHTML('<span class="accesshide">' + this.CONSTANTS.QUESTION + '</span> ' + number);
},
/**
@ -112,7 +126,19 @@ Y.Moodle.mod_quiz.util.slot = {
},
/**
* Returns the previous slot to the give slot.
* Returns a list of all slot elements on the page that have numbers. Excudes description questions.
*
* @method getSlots
* @return {node[]} An array containing slot nodes.
*/
getNumberedSlots: function() {
var selector = this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL;
selector += ' ' + this.SELECTORS.SLOT + ':not(' + this.SELECTORS.QUESTIONTYPEDESCRIPTION + ')';
return Y.all(selector);
},
/**
* Returns the previous slot to the given slot.
*
* @method getPrevious
* @param slot Slot node
@ -122,27 +148,198 @@ Y.Moodle.mod_quiz.util.slot = {
return slot.previous(this.SELECTORS.SLOT);
},
/**
* Returns the previous numbered slot to the given slot.
*
* Ignores slots containing description question types.
*
* @method getPrevious
* @param slot Slot node
* @return {node|false} The previous slot node or false.
*/
getPreviousNumbered: function(slot) {
return slot.previous(this.SELECTORS.SLOT + ':not(' + this.SELECTORS.QUESTIONTYPEDESCRIPTION + ')');
},
/**
* Reset the order of the numbers given to each slot.
*
* @method reorder_slots
* @method reorderSlots
* @return void
*/
reorder_slots: function() {
reorderSlots: function() {
// Get list of slot nodes.
var slots = this.getSlots();
// Loop through slots incrementing the number each time.
slots.each(function(slot) {
var previousSlot = this.getPrevious(slot),
previousslotnumber = 0;
if(previousSlot){
if (!Y.Moodle.mod_quiz.util.page.getPageFromSlot(slot)) {
// Move the next page to the front.
var nextpage = slot.next(Y.Moodle.mod_quiz.util.page.SELECTORS.PAGE);
slot.swap(nextpage);
}
var previousSlot = this.getPreviousNumbered(slot);
previousslotnumber = 0;
if (slot.hasClass(this.CSS.QUESTIONTYPEDESCRIPTION)) {
return;
}
if (previousSlot) {
previousslotnumber = this.getNumber(previousSlot);
}
// Set slot number.
this.setNumber(slot, previousslotnumber + 1);
}, this);
},
/**
* Remove a slot and related elements from the list of slots.
*
* @method remove
* @param slot Slot node
* @return void
*/
remove: function(slot) {
var page = Y.Moodle.mod_quiz.util.page.getPageFromSlot(slot);
slot.remove();
// Is the page empty.
if (!Y.Moodle.mod_quiz.util.page.isEmpty(page)) {
return;
}
// If so remove it. Including add menu and page break.
Y.Moodle.mod_quiz.util.page.remove(page);
},
/**
* Returns a list of all page break elements on the page.
*
* @method getPageBreaks
* @return {node[]} An array containing page break nodes.
*/
getPageBreaks: function() {
var selector = this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL;
selector += ' ' + this.SELECTORS.SLOT + this.SELECTORS.PAGEBREAK;
return Y.all(selector);
},
/**
* Retrieve the page break element item from the given slot.
*
* @method getPageBreak
* @param slot Slot node
* @return {Node|null} The Page Break Node.
*/
getPageBreak: function(slot) {
return Y.one(slot).one(this.SELECTORS.PAGEBREAK);
},
/**
* Add a page break and related elements to the list of slots.
*
* @method addPageBreak
* @param beforenode Int | Node | HTMLElement | String to add
* @return pagebreak PageBreak node
*/
addPageBreak: function(slot) {
var nodetext = M.mod_quiz.resource_toolbox.get('config').addpageiconhtml;
nodetext = nodetext.replace('%%SLOT%%', this.getNumber(slot));
var pagebreak = Y.Node.create(nodetext);
slot.one('div').insert(pagebreak, 'after');
return pagebreak;
},
/**
* Remove a pagebreak from the given slot.
*
* @method removePageBreak
* @param slot Slot node
* @return boolean
*/
removePageBreak: function(slot) {
var pagebreak = this.getPageBreak(slot);
if (!pagebreak) {
return false;
}
pagebreak.remove();
return true;
},
/**
* Reorder each pagebreak by iterating through each related slot.
*
* @method reorderPageBreaks
* @return void
*/
reorderPageBreaks: function() {
// Get list of slot nodes.
var slots = this.getSlots(), slotnumber = 0;
// Loop through slots incrementing the number each time.
slots.each (function(slot, key) {
slotnumber++;
var pagebreak = this.getPageBreak(slot);
// Last slot won't have a page break.
if (!pagebreak && key === slots.size() - 1) {
return;
}
// No pagebreak and not last slot. Add one.
if (!pagebreak && key !== slots.size() - 1) {
pagebreak = this.addPageBreak(slot);
}
// Remove last page break if there is one.
if (pagebreak && key === slots.size() - 1) {
this.removePageBreak(slot);
}
// Get page break anchor element.
var pagebreaklink = pagebreak.get('childNodes').item(0);
// Get the correct title.
var nextactivity = slot.next('li.activity');
var titlename = '', action = '', uri = M.cfg.wwwroot;
var iconsrc = uri + '/theme/image.php?theme=clean&component=core';
// IE8 can't handle svg images.
if (Y.one('body.ie8')) {
iconsrc += '&svg=e%2F0';
}
if (Y.Moodle.mod_quiz.util.page.isPage(nextactivity)) {
action = titlename = 'removepagebreak';
iconsrc += '&image=e%2Fremove_page_break';
} else {
action = titlename = 'addpagebreak';
iconsrc += '&image=e%2Finsert_page_break';
}
var title = M.util.get_string(titlename, 'quiz');
// Update the link and image titles
pagebreaklink.set('title', title);
pagebreaklink.setData('action', action);
// Update the image title.
var icon = pagebreaklink.one(this.SELECTORS.ICON);
icon.set('title', title);
// Update the image src.
icon.set('src', iconsrc);
// Get anchor url parameters as an associative array.
var params = Y.QueryString.parse(pagebreaklink.get('href'));
// Update slot number.
params.slot = slotnumber;
// Create the new url.
var newurl = '';
for (var index in params) {
if (newurl.length) {
newurl += "&";
}
newurl += index + "=" + params[index];
}
// Update the anchor.
pagebreaklink.set('href', newurl);
}, this);
}
};

View File

@ -14,7 +14,6 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
this.groups = ['resource'];
this.samenodeclass = CSS.ACTIVITY;
this.parentnodeclass = CSS.SECTION;
//this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
this.samenodelabel = {
@ -168,12 +167,11 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
var responsetext = Y.JSON.parse(response.responseText);
var params = {element: dragnode, visible: responsetext.visible};
M.mod_quiz.quizbase.invoke_function('set_visibility_resource_ui', params);
Y.Moodle.mod_quiz.util.slot.reorder_slots();
this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
window.setTimeout(function() {
spinner.hide();
}, 250);
window.location.reload(true);
M.mod_quiz.resource_toolbox.reorganise_edit_page();
},
failure: function(tid, response) {
this.ajax_failure(response);

View File

@ -10,7 +10,9 @@
"moodle-core-dragdrop",
"moodle-core-notification",
"moodle-mod_quiz-quizbase",
"moodle-mod_quiz-util",
"moodle-mod_quiz-util-base",
"moodle-mod_quiz-util-page",
"moodle-mod_quiz-util-slot",
"moodle-course-util"
]
}

View File

@ -77,9 +77,9 @@ M.mod_quiz.edit.swap_sections = function(Y, node1, node2) {
SECTIONADDMENUS : 'section_add_menus'
};
var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.mod_quiz.edit.get_section_selector(Y));
var sectionlist = Y.Node.all('.' + CSS.COURSECONTENT + ' ' + M.mod_quiz.edit.get_section_selector(Y));
// Swap menus.
sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
sectionlist.item(node1).one('.' + CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.' + CSS.SECTIONADDMENUS));
};
/**
@ -115,7 +115,7 @@ M.mod_quiz.edit.process_sections = function(Y, sectionlist, response, sectionfro
for (var i = sectionfrom; i <= sectionto; i++) {
// Update section title.
sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
sectionlist.item(i).one('.' + CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
// Update move icon.
ele = sectionlist.item(i).one(SELECTORS.SECTIONLEFTSIDE);

View File

@ -101,10 +101,10 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// The user is deleting the activity.
this.delete_with_confirmation(ev, node, activity, action);
break;
case 'linkpage':
case 'unlinkpage':
// The user is linking or unlinking pages.
this.link_page(ev, node, activity, action);
case 'addpagebreak':
case 'removepagebreak':
// The user is adding or removing a page break.
this.update_page_break(ev, node, activity, action);
break;
default:
// Nothing to do here!
@ -140,10 +140,10 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @chainable
*/
delete_with_confirmation: function(ev, button, activity) {
// Prevent the default button action
// Prevent the default button action.
ev.preventDefault();
// Get the element we're working on
// Get the element we're working on.
var element = activity,
// Create confirm string (different if element has or does not have name)
confirmstring = '',
@ -160,19 +160,24 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// If it is confirmed.
confirm.on('complete-yes', function() {
// Actually remove the element.
element.remove();
Y.Moodle.mod_quiz.util.slot.reorder_slots();
var spinner = this.add_spinner(element);
var data = {
'class': 'resource',
'action': 'DELETE',
'id': Y.Moodle.mod_quiz.util.slot.getId(element)
};
this.send_request(data);
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
window.location.reload(true);
this.send_request(data, spinner, function(response) {
if (response.deleted) {
// Actually remove the element.
Y.Moodle.mod_quiz.util.slot.remove(element);
this.reorganise_edit_page();
if (M.core.actionmenu && M.core.actionmenu.instance) {
M.core.actionmenu.instance.hideMenu();
}
} else {
window.location.reload(true);
}
});
}, this);
@ -215,12 +220,12 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
M.core.actionmenu.instance.hideMenu();
}
// Try to retrieve the existing string from the server
// Try to retrieve the existing string from the server.
if (response.instancemaxmark) {
maxmarktext = response.instancemaxmark;
}
// Create the editor and submit button
// Create the editor and submit button.
var editform = Y.Node.create('<form action="#" />');
var editinstructions = Y.Node.create('<span class="' + CSS.EDITINSTRUCTIONS + '" id="id_editinstructions" />')
.set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
@ -232,7 +237,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
'size' : parseInt(this.get('config').questiondecimalpoints, 10) + 2
});
// Clear the existing content and put the editor in
// Clear the existing content and put the editor in.
editform.appendChild(editor);
editform.setData('anchor', anchor);
instance.insert(editinstructions, 'before');
@ -247,7 +252,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
// We hide various components whilst editing:
activity.addClass(CSS.EDITINGMAXMARK);
// Focus and select the editor text
// Focus and select the editor text.
editor.focus().select();
// Cancel the edit if we lose focus or the escape key is pressed.
@ -272,7 +277,7 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* @param {String} originalmaxmark The original maxmark the activity or resource had.
*/
edit_maxmark_submit : function(ev, activity, originalmaxmark) {
// We don't actually want to submit anything
// We don't actually want to submit anything.
ev.preventDefault();
var newmaxmark = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYMAXMARK).get('value'));
var spinner = this.add_spinner(activity);
@ -350,97 +355,59 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
* the other slots
*
* @protected
* @method link_page
* @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.
* @chainable
*/
link_page: function(ev, button, activity, action) {
update_page_break: function(ev, button, activity, action) {
// Prevent the default button action
ev.preventDefault();
activity = activity.next('li.activity.slot');
var spinner = this.add_spinner(activity),
nextactivity = activity.next('li.activity.slot');
var spinner = this.add_spinner(nextactivity),
slotid = 0;
var value = action === 'linkpage' ? 1:2;
var value = action === 'removepagebreak' ? 1 : 2;
var data = {
'class': 'resource',
'field': 'linkslottopage',
'field': 'updatepagebreak',
'id': slotid,
'value': value
};
slotid = Y.Moodle.mod_quiz.util.slot.getId(activity);
slotid = Y.Moodle.mod_quiz.util.slot.getId(nextactivity);
if (slotid) {
data.id = Number(slotid);
}
this.send_request(data, spinner, function(response) {
window.location.reload(true);
// if (response.slots) {
// this.repaginate_slots(response.slots);
// }
if (response.slots) {
if (action === 'addpagebreak') {
Y.Moodle.mod_quiz.util.page.add(activity);
} else {
var page = activity.next(Y.Moodle.mod_quiz.util.page.SELECTORS.PAGE);
Y.Moodle.mod_quiz.util.page.remove(page, true);
}
this.reorganise_edit_page();
} else {
window.location.reload(true);
}
});
return this;
},
repaginate_slots: function(slots) {
this.slots = slots;
var section = Y.one(SELECTOR.PAGECONTENT + ' ' + SELECTOR.SECTIONUL),
activities = section.all(SELECTOR.ACTIVITYLI);
activities.each(function(node) {
// What element is it? page/slot/link
// what is the current slot?
var type;
var slot;
if(node.hasClass(CSS.PAGE)){
type = this.NODE_PAGE;
slot = node.next(SELECTOR.SLOTLI);
} else if (node.hasClass(CSS.SLOT)){
type = this.NODE_SLOT;
slot = node;
} else if (node.hasClass(CSS.JOIN)){
type = this.NODE_JOIN;
slot = node.previous(SELECTOR.SLOTLI);
}
// getSlotnumber() Should be a method of util.slot
var slotnumber = Number(Y.Moodle.mod_quiz.util.slot.getNumber(slot));
if(!type){
// Nothing we can do.
return;
}
// Is it correct?
if(!this.slots.hasOwnProperty(slotnumber)){
// An error. We should handle this.
return;
}
var slotdata = this.slots[slotnumber];
if(type === this.NODE_PAGE){
// Get page number
var pagenumber = Y.Moodle.mod_quiz.util.page.getNumber(node);
// Is the page number correct?
if (slotdata.page === pagenumber) {
console.log('slotdata.page == pagenumber return');
return;
}
if (pagenumber < slotdata.page) {
// Remove page node.
node.remove();
}
else {
// Add page node.
console.log('pagenumber > slotdata.page update page number');
}
}
}, this);
/**
* Reorganise the UI after every edit action.
*
* @protected
* @method reorganise_edit_page
*/
reorganise_edit_page: function() {
Y.Moodle.mod_quiz.util.slot.reorderSlots();
Y.Moodle.mod_quiz.util.slot.reorderPageBreaks();
Y.Moodle.mod_quiz.util.page.reorderPages();
},
NAME : 'mod_quiz-resource-toolbox',

View File

@ -14,13 +14,23 @@ Y.namespace('Moodle.mod_quiz.util.page');
* @static
*/
Y.Moodle.mod_quiz.util.page = {
CSS: {
PAGE : 'page'
},
CONSTANTS: {
PAGEIDPREFIX : 'page-',
PAGENUMBERPREFIX : 'Page '
ACTIONMENUIDPREFIX: 'action-menu-',
ACTIONMENUBARIDSUFFIX: '-menubar',
ACTIONMENUMENUIDSUFFIX: '-menu',
PAGEIDPREFIX: 'page-',
PAGENUMBERPREFIX: M.util.get_string('page', 'moodle') + ' '
},
SELECTORS: {
ACTIONMENU: 'div.moodle-actionmenu',
ACTIONMENUBAR: 'ul.menubar',
ACTIONMENUMENU: 'ul.menu',
PAGE: 'li.page',
INSTANCENAME: '.instancename'
INSTANCENAME: '.instancename',
NUMBER: 'span.text'
},
/**
@ -35,7 +45,18 @@ Y.Moodle.mod_quiz.util.page = {
},
/**
* Determines the page ID for the provided page.
* Retrieve the page item from one of it's previous siblings.
*
* @method getPageFromSlot
* @param pagecomponent {Node} The component Node.
* @return {Node|null} The Page Node.
*/
getPageFromSlot: function(slot) {
return Y.one(slot).previous(this.SELECTORS.PAGE);
},
/**
* Returns the page ID for the provided page.
*
* @method getId
* @param page {Node} The page to find an ID for.
@ -54,6 +75,18 @@ Y.Moodle.mod_quiz.util.page = {
return false;
},
/**
* Updates the page id for the provided page.
*
* @method setId
* @param page {Node} The page to update the number for.
* @param id int The id value.
* @return void
*/
setId: function(page, id) {
page.set('id', this.CONSTANTS.PAGEIDPREFIX + id);
},
/**
* Determines the page name for the provided page.
*
@ -77,8 +110,8 @@ Y.Moodle.mod_quiz.util.page = {
* @return {Number|false} The number of the page in question or false if no number was found.
*/
getNumber: function(page) {
// We perform a simple substitution operation to get the ID.
var number = page.get('text').replace(
// We perform a simple substitution operation to get the number.
var number = page.one(this.SELECTORS.NUMBER).get('text').replace(
this.CONSTANTS.PAGENUMBERPREFIX, '');
// Attempt to validate the ID.
@ -87,5 +120,205 @@ Y.Moodle.mod_quiz.util.page = {
return number;
}
return false;
},
/**
* Updates the page number for the provided page.
*
* @method setNumber
* @param page {Node} The page to update the number for.
* @return void
*/
setNumber: function(page, number) {
page.one(this.SELECTORS.NUMBER).set('text', this.CONSTANTS.PAGENUMBERPREFIX + number);
},
/**
* Returns a list of all page elements.
*
* @method getPages
* @return {node[]} An array containing page nodes.
*/
getPages: function() {
return Y.all(Y.Moodle.mod_quiz.util.slot.SELECTORS.PAGECONTENT + ' ' + Y.Moodle.mod_quiz.util.slot.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.PAGE);
},
/**
* Is the given element a page element?
*
* @method isPage
* @param page Page node
* @return boolean
*/
isPage: function(page) {
if (!page) {
return false;
}
return page.hasClass(this.CSS.PAGE);
},
/**
* Does the page have atleast one slot?
*
* @method isEmpty
* @param page Page node
* @return boolean
*/
isEmpty: function(page) {
var activity = page.next('li.activity');
if (!activity) {
return true;
}
return !activity.hasClass('slot');
},
/**
* Add a page and related elements to the list of slots.
*
* @method add
* @param beforenode Int | Node | HTMLElement | String to add
* @return page Page node
*/
add: function(beforenode) {
var pagenumber = this.getNumber(this.getPageFromSlot(beforenode)) + 1;
var pagehtml = M.mod_quiz.resource_toolbox.get('config').pagehtml;
// Normalise the page number.
pagehtml = pagehtml.replace(/%%PAGENUMBER%%/g, pagenumber);
// Create the page node.
var page = Y.Node.create(pagehtml);
// Assign is as a drop target.
YUI().use('dd-drop', function(Y) {
var drop = new Y.DD.Drop({
node: page,
groups: M.mod_quiz.dragres.groups
});
page.drop = drop;
});
// Insert in the correct place.
beforenode.insert(page, 'after');
// Enhance the add menu to make if fully visible and clickable.
M.core.actionmenu.newDOMNode(page);
return page;
},
/**
* Remove a page and related elements from the list of slots.
*
* @method remove
* @param page Page node
* @return void
*/
remove: function(page, keeppagebreak) {
// Remove page break from previous slot.
var previousslot = page.previous(Y.Moodle.mod_quiz.util.slot.SELECTORS.SLOT);
if (!keeppagebreak && previousslot) {
Y.Moodle.mod_quiz.util.slot.removePageBreak(previousslot);
}
page.remove();
},
/**
* Reset the order of the numbers given to each page.
*
* @method reorderPages
* @return void
*/
reorderPages: function() {
// Get list of page nodes.
var pages = this.getPages(), currentpagenumber = 0;
// Loop through pages incrementing the number each time.
pages.each(function(page) {
// Is the page empty?
if (this.isEmpty(page)) {
var keeppagebreak = page.next('li.slot') ? true : false;
this.remove(page, keeppagebreak);
return;
}
currentpagenumber++;
// Set page number.
this.setNumber(page, currentpagenumber);
this.setId(page, currentpagenumber);
}, this);
// Reorder action menus
this.reorderActionMenus();
},
/**
* Reset the order of the numbers given to each action menu.
*
* @method reorderActionMenus
* @return void
*/
reorderActionMenus: function() {
// Get list of action menu nodes.
var actionmenus = this.getActionMenus();
// Loop through pages incrementing the number each time.
actionmenus.each(function(actionmenu, key) {
var previousActionMenu = actionmenus.item(key - 1);
previousActionMenunumber = 0;
if (previousActionMenu) {
previousActionMenunumber = this.getActionMenuId(previousActionMenu);
}
var id = previousActionMenunumber + 1;
// Set menu id.
this.setActionMenuId(actionmenu, id);
// Update action-menu-1-menubar
var menubar = actionmenu.one(this.SELECTORS.ACTIONMENUBAR);
menubar.set('id', this.CONSTANTS.ACTIONMENUIDPREFIX + id + this.CONSTANTS.ACTIONMENUBARIDSUFFIX);
// Update action-menu-1-menu
var menumenu = actionmenu.one(this.SELECTORS.ACTIONMENUMENU);
menumenu.set('id', this.CONSTANTS.ACTIONMENUIDPREFIX + id + this.CONSTANTS.ACTIONMENUMENUIDSUFFIX);
}, this);
},
/**
* Returns a list of all page elements.
*
* @method getActionMenus
* @return {node[]} An array containing page nodes.
*/
getActionMenus: function() {
return Y.all(Y.Moodle.mod_quiz.util.slot.SELECTORS.PAGECONTENT + ' ' + Y.Moodle.mod_quiz.util.slot.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.ACTIONMENU);
},
/**
* Returns the ID for the provided action menu.
*
* @method getId
* @param actionmenu {Node} The actionmenu to find an ID for.
* @return {Number|false} The ID of the actionmenu in question or false if no ID was found.
*/
getActionMenuId: function(actionmenu) {
// We perform a simple substitution operation to get the ID.
var id = actionmenu.get('id').replace(
this.CONSTANTS.ACTIONMENUIDPREFIX, '');
// Attempt to validate the ID.
id = parseInt(id, 10);
if (typeof id === 'number' && isFinite(id)) {
return id;
}
return false;
},
/**
* Updates the page id for the provided page.
*
* @method setId
* @param page {Node} The page to update the number for.
* @param id int The id value.
* @return void
*/
setActionMenuId: function(actionmenu, id) {
actionmenu.set('id', this.CONSTANTS.ACTIONMENUIDPREFIX + id);
}
};

View File

@ -14,14 +14,22 @@ Y.namespace('Moodle.mod_quiz.util.slot');
* @static
*/
Y.Moodle.mod_quiz.util.slot = {
CSS: {
SLOT : 'slot',
QUESTIONTYPEDESCRIPTION : 'qtype_description'
},
CONSTANTS: {
SLOTIDPREFIX : 'slot-'
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'
},
@ -79,7 +87,12 @@ Y.Moodle.mod_quiz.util.slot = {
* @return {Number|false} The number of the slot in question or false if no number was found.
*/
getNumber: function(slot) {
var number = slot.one(this.SELECTORS.NUMBER).get('text');
if (!slot) {
return false;
}
// We perform a simple substitution operation to get the number.
var number = slot.one(this.SELECTORS.NUMBER).get('text').replace(
this.CONSTANTS.QUESTION, '');
// Attempt to validate the ID.
number = parseInt(number, 10);
if (typeof number === 'number' && isFinite(number)) {
@ -96,7 +109,8 @@ Y.Moodle.mod_quiz.util.slot = {
* @return void
*/
setNumber: function(slot, number) {
slot.one(this.SELECTORS.NUMBER).set('text', number);
var numbernode = slot.one(this.SELECTORS.NUMBER);
numbernode.setHTML('<span class="accesshide">' + this.CONSTANTS.QUESTION + '</span> ' + number);
},
/**
@ -110,7 +124,19 @@ Y.Moodle.mod_quiz.util.slot = {
},
/**
* Returns the previous slot to the give slot.
* Returns a list of all slot elements on the page that have numbers. Excudes description questions.
*
* @method getSlots
* @return {node[]} An array containing slot nodes.
*/
getNumberedSlots: function() {
var selector = this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL;
selector += ' ' + this.SELECTORS.SLOT + ':not(' + this.SELECTORS.QUESTIONTYPEDESCRIPTION + ')';
return Y.all(selector);
},
/**
* Returns the previous slot to the given slot.
*
* @method getPrevious
* @param slot Slot node
@ -120,26 +146,197 @@ Y.Moodle.mod_quiz.util.slot = {
return slot.previous(this.SELECTORS.SLOT);
},
/**
* Returns the previous numbered slot to the given slot.
*
* Ignores slots containing description question types.
*
* @method getPrevious
* @param slot Slot node
* @return {node|false} The previous slot node or false.
*/
getPreviousNumbered: function(slot) {
return slot.previous(this.SELECTORS.SLOT + ':not(' + this.SELECTORS.QUESTIONTYPEDESCRIPTION + ')');
},
/**
* Reset the order of the numbers given to each slot.
*
* @method reorder_slots
* @method reorderSlots
* @return void
*/
reorder_slots: function() {
reorderSlots: function() {
// Get list of slot nodes.
var slots = this.getSlots();
// Loop through slots incrementing the number each time.
slots.each(function(slot) {
var previousSlot = this.getPrevious(slot),
previousslotnumber = 0;
if(previousSlot){
if (!Y.Moodle.mod_quiz.util.page.getPageFromSlot(slot)) {
// Move the next page to the front.
var nextpage = slot.next(Y.Moodle.mod_quiz.util.page.SELECTORS.PAGE);
slot.swap(nextpage);
}
var previousSlot = this.getPreviousNumbered(slot);
previousslotnumber = 0;
if (slot.hasClass(this.CSS.QUESTIONTYPEDESCRIPTION)) {
return;
}
if (previousSlot) {
previousslotnumber = this.getNumber(previousSlot);
}
// Set slot number.
this.setNumber(slot, previousslotnumber + 1);
}, this);
},
/**
* Remove a slot and related elements from the list of slots.
*
* @method remove
* @param slot Slot node
* @return void
*/
remove: function(slot) {
var page = Y.Moodle.mod_quiz.util.page.getPageFromSlot(slot);
slot.remove();
// Is the page empty.
if (!Y.Moodle.mod_quiz.util.page.isEmpty(page)) {
return;
}
// If so remove it. Including add menu and page break.
Y.Moodle.mod_quiz.util.page.remove(page);
},
/**
* Returns a list of all page break elements on the page.
*
* @method getPageBreaks
* @return {node[]} An array containing page break nodes.
*/
getPageBreaks: function() {
var selector = this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL;
selector += ' ' + this.SELECTORS.SLOT + this.SELECTORS.PAGEBREAK;
return Y.all(selector);
},
/**
* Retrieve the page break element item from the given slot.
*
* @method getPageBreak
* @param slot Slot node
* @return {Node|null} The Page Break Node.
*/
getPageBreak: function(slot) {
return Y.one(slot).one(this.SELECTORS.PAGEBREAK);
},
/**
* Add a page break and related elements to the list of slots.
*
* @method addPageBreak
* @param beforenode Int | Node | HTMLElement | String to add
* @return pagebreak PageBreak node
*/
addPageBreak: function(slot) {
var nodetext = M.mod_quiz.resource_toolbox.get('config').addpageiconhtml;
nodetext = nodetext.replace('%%SLOT%%', this.getNumber(slot));
var pagebreak = Y.Node.create(nodetext);
slot.one('div').insert(pagebreak, 'after');
return pagebreak;
},
/**
* Remove a pagebreak from the given slot.
*
* @method removePageBreak
* @param slot Slot node
* @return boolean
*/
removePageBreak: function(slot) {
var pagebreak = this.getPageBreak(slot);
if (!pagebreak) {
return false;
}
pagebreak.remove();
return true;
},
/**
* Reorder each pagebreak by iterating through each related slot.
*
* @method reorderPageBreaks
* @return void
*/
reorderPageBreaks: function() {
// Get list of slot nodes.
var slots = this.getSlots(), slotnumber = 0;
// Loop through slots incrementing the number each time.
slots.each (function(slot, key) {
slotnumber++;
var pagebreak = this.getPageBreak(slot);
// Last slot won't have a page break.
if (!pagebreak && key === slots.size() - 1) {
return;
}
// No pagebreak and not last slot. Add one.
if (!pagebreak && key !== slots.size() - 1) {
pagebreak = this.addPageBreak(slot);
}
// Remove last page break if there is one.
if (pagebreak && key === slots.size() - 1) {
this.removePageBreak(slot);
}
// Get page break anchor element.
var pagebreaklink = pagebreak.get('childNodes').item(0);
// Get the correct title.
var nextactivity = slot.next('li.activity');
var titlename = '', action = '', uri = M.cfg.wwwroot;
var iconsrc = uri + '/theme/image.php?theme=clean&component=core';
// IE8 can't handle svg images.
if (Y.one('body.ie8')) {
iconsrc += '&svg=e%2F0';
}
if (Y.Moodle.mod_quiz.util.page.isPage(nextactivity)) {
action = titlename = 'removepagebreak';
iconsrc += '&image=e%2Fremove_page_break';
} else {
action = titlename = 'addpagebreak';
iconsrc += '&image=e%2Finsert_page_break';
}
var title = M.util.get_string(titlename, 'quiz');
// Update the link and image titles
pagebreaklink.set('title', title);
pagebreaklink.setData('action', action);
// Update the image title.
var icon = pagebreaklink.one(this.SELECTORS.ICON);
icon.set('title', title);
// Update the image src.
icon.set('src', iconsrc);
// Get anchor url parameters as an associative array.
var params = Y.QueryString.parse(pagebreaklink.get('href'));
// Update slot number.
params.slot = slotnumber;
// Create the new url.
var newurl = '';
for (var index in params) {
if (newurl.length) {
newurl += "&";
}
newurl += index + "=" + params[index];
}
// Update the anchor.
pagebreaklink.set('href', newurl);
}, this);
}
};