diff --git a/lib/amd/build/scroll_manager.min.js b/lib/amd/build/scroll_manager.min.js
new file mode 100644
index 00000000000..c551ee0806b
--- /dev/null
+++ b/lib/amd/build/scroll_manager.min.js
@@ -0,0 +1,23 @@
+define("core/scroll_manager",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.watchScrollButtonSaves=_exports.scrollToSavedPosition=_exports.saveScrollPositionToForm=_exports.saveScrollPos=_exports.initLinksScrollPos=void 0;
+/**
+ * Scroll manager is a class that help with saving the scroll positing when you
+ * click on an action icon, and then when the page is reloaded after processing
+ * the action, it scrolls you to exactly where you were. This is much nicer for
+ * the user.
+ *
+ * To use this in your code, you need to ensure that:
+ * 1. The button that triggers the action has to have a click event handler that
+ * calls saveScrollPos()
+ * 2. After doing the processing, the redirect() function will add 'mdlscrollto'
+ * parameter into the redirect url automatically.
+ * 3. Finally, on the page that is reloaded (which should be the same as the one
+ * the user started on) you need to call scrollToSavedPosition()
+ * on page load.
+ *
+ * @module core/scroll_manager
+ * @copyright 2021 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+let scrollingElement=null;const getScrollingElement=()=>{if(null===scrollingElement){const page=document.getElementById("page");scrollingElement=(element=>{const hasScrollableContent=element.scrollHeight>element.clientHeight,isOverflowHidden=-1!==window.getComputedStyle(element).overflowY.indexOf("hidden");return hasScrollableContent&&!isOverflowHidden})(page)?page:document.scrollingElement}return scrollingElement},getScrollPos=()=>getScrollingElement().scrollTop;_exports.saveScrollPos=elementId=>{const form=document.getElementById(elementId).closest("form");form&&saveScrollPositionToForm(form)};_exports.watchScrollButtonSaves=()=>{document.addEventListener("click",(e=>{const button=e.target.closest('[data-savescrollposition="true"]');button&&saveScrollPositionToForm(button.form)}))};const saveScrollPositionToForm=form=>{(form=>{const element=form.querySelector("input[name=mdlscrollto]");if(element)return element;const scrollPos=document.createElement("input");return scrollPos.type="hidden",scrollPos.name="mdlscrollto",form.appendChild(scrollPos),scrollPos})(form).value=getScrollPos()};_exports.saveScrollPositionToForm=saveScrollPositionToForm;_exports.initLinksScrollPos=()=>{document.addEventListener("click",(e=>{if(!e.target.closest("a[data-save-scroll=true]"))return;e.preventDefault();const url=new URL(e.target.href);url.searchParams.set("mdlscrollto",getScrollPos()),window.location=url}))};_exports.scrollToSavedPosition=()=>{const url=new URL(window.location.href);if(!url.searchParams.has("mdlscrollto"))return;const scrollPosition=url.searchParams.get("mdlscrollto"),scrollingElement=getScrollingElement();scrollingElement.scrollTo(0,scrollPosition),document.addEventListener("DOMContentLoaded",(()=>{scrollingElement.scrollTo(0,scrollPosition)}))}}));
+
+//# sourceMappingURL=scroll_manager.min.js.map
\ No newline at end of file
diff --git a/lib/amd/build/scroll_manager.min.js.map b/lib/amd/build/scroll_manager.min.js.map
new file mode 100644
index 00000000000..d3db2066395
--- /dev/null
+++ b/lib/amd/build/scroll_manager.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"scroll_manager.min.js","sources":["../src/scroll_manager.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Scroll manager is a class that help with saving the scroll positing when you\n * click on an action icon, and then when the page is reloaded after processing\n * the action, it scrolls you to exactly where you were. This is much nicer for\n * the user.\n *\n * To use this in your code, you need to ensure that:\n * 1. The button that triggers the action has to have a click event handler that\n * calls saveScrollPos()\n * 2. After doing the processing, the redirect() function will add 'mdlscrollto'\n * parameter into the redirect url automatically.\n * 3. Finally, on the page that is reloaded (which should be the same as the one\n * the user started on) you need to call scrollToSavedPosition()\n * on page load.\n *\n * @module core/scroll_manager\n * @copyright 2021 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/** @property {HTMLElement} scrollingElement the current scrolling element. */\nlet scrollingElement = null;\n\n/**\n * Is the element scrollable?\n *\n * @param {HTMLElement} element Element.\n * @returns {boolean}\n */\nconst isScrollable = (element) => {\n // Check if the element has scrollable content.\n const hasScrollableContent = element.scrollHeight > element.clientHeight;\n\n // If 'overflow-y' is set to hidden, the scroll bar is't show.\n const elementOverflow = window.getComputedStyle(element).overflowY;\n const isOverflowHidden = elementOverflow.indexOf('hidden') !== -1;\n\n return hasScrollableContent && !isOverflowHidden;\n};\n\n/**\n * Get the scrolling element.\n *\n * @returns {HTMLElement}\n */\nconst getScrollingElement = () => {\n if (scrollingElement === null) {\n const page = document.getElementById('page');\n if (isScrollable(page)) {\n scrollingElement = page;\n } else {\n scrollingElement = document.scrollingElement;\n }\n }\n\n return scrollingElement;\n};\n\n/**\n * Get current scroll position.\n *\n * @returns {Number} Scroll position.\n */\nconst getScrollPos = () => {\n const scrollingElement = getScrollingElement();\n\n return scrollingElement.scrollTop;\n};\n\n/**\n * Get the scroll position for this form.\n *\n * @param {HTMLFormElement} form\n * @returns {HTMLInputElement}\n */\nconst getScrollPositionElement = (form) => {\n const element = form.querySelector('input[name=mdlscrollto]');\n if (element) {\n return element;\n }\n\n const scrollPos = document.createElement('input');\n scrollPos.type = 'hidden';\n scrollPos.name = 'mdlscrollto';\n form.appendChild(scrollPos);\n\n return scrollPos;\n};\n\n/**\n * In the form that contains the element, set the value of the form field with\n * name mdlscrollto to the current scroll position. If there is no element with\n * that name, it creates a hidden form field with that name within the form.\n *\n * @param {string} elementId The element in the form.\n */\nexport const saveScrollPos = (elementId) => {\n const element = document.getElementById(elementId);\n const form = element.closest('form');\n if (!form) {\n return;\n }\n\n saveScrollPositionToForm(form);\n};\n\n/**\n * Init event handlers for all links with data-savescrollposition=true.\n * Set the value to the closest form.\n */\nexport const watchScrollButtonSaves = () => {\n document.addEventListener('click', (e) => {\n const button = e.target.closest('[data-savescrollposition=\"true\"]');\n if (button) {\n saveScrollPositionToForm(button.form);\n }\n });\n};\n\n/**\n * Save the position to form.\n *\n * @param {Object} form The form is saved scroll position.\n */\nexport const saveScrollPositionToForm = (form) => {\n getScrollPositionElement(form).value = getScrollPos();\n};\n\n/**\n * Init event handlers for all links with data-save-scroll=true.\n * Handle to add mdlscrollto parameter to link using js when we click on the link.\n *\n */\nexport const initLinksScrollPos = () => {\n document.addEventListener('click', (e) => {\n const link = e.target.closest('a[data-save-scroll=true]');\n if (!link) {\n return;\n }\n\n e.preventDefault();\n const url = new URL(e.target.href);\n url.searchParams.set('mdlscrollto', getScrollPos());\n window.location = url;\n });\n};\n\n/**\n * If there is a parameter like mdlscrollto=123 in the URL, scroll to that saved position.\n */\nexport const scrollToSavedPosition = () => {\n const url = new URL(window.location.href);\n if (!url.searchParams.has('mdlscrollto')) {\n return;\n }\n\n const scrollPosition = url.searchParams.get('mdlscrollto');\n\n // Event onDOMReady is the effective one here. I am leaving the immediate call to\n // window.scrollTo in case it reduces flicker.\n const scrollingElement = getScrollingElement();\n scrollingElement.scrollTo(0, scrollPosition);\n document.addEventListener('DOMContentLoaded', () => {\n scrollingElement.scrollTo(0, scrollPosition);\n });\n};\n"],"names":["scrollingElement","getScrollingElement","page","document","getElementById","element","hasScrollableContent","scrollHeight","clientHeight","isOverflowHidden","window","getComputedStyle","overflowY","indexOf","isScrollable","getScrollPos","scrollTop","elementId","form","closest","saveScrollPositionToForm","addEventListener","e","button","target","querySelector","scrollPos","createElement","type","name","appendChild","getScrollPositionElement","value","preventDefault","url","URL","href","searchParams","set","location","has","scrollPosition","get","scrollTo"],"mappings":";;;;;;;;;;;;;;;;;;;;IAoCIA,iBAAmB,WAwBjBC,oBAAsB,QACC,OAArBD,iBAA2B,OACrBE,KAAOC,SAASC,eAAe,QAEjCJ,iBApBUK,CAAAA,gBAEZC,qBAAuBD,QAAQE,aAAeF,QAAQG,aAItDC,kBAA0D,IADxCC,OAAOC,iBAAiBN,SAASO,UAChBC,QAAQ,iBAE1CP,uBAAyBG,kBAWxBK,CAAaZ,MACMA,KAEAC,SAASH,wBAI7BA,kBAQLe,aAAe,IACQd,sBAEDe,iCA8BEC,kBAEpBC,KADUf,SAASC,eAAea,WACnBE,QAAQ,QACxBD,MAILE,yBAAyBF,uCAOS,KAClCf,SAASkB,iBAAiB,SAAUC,UAC1BC,OAASD,EAAEE,OAAOL,QAAQ,oCAC5BI,QACAH,yBAAyBG,OAAOL,gBAU/BE,yBAA4BF,OAjDPA,CAAAA,aACxBb,QAAUa,KAAKO,cAAc,8BAC/BpB,eACOA,cAGLqB,UAAYvB,SAASwB,cAAc,gBACzCD,UAAUE,KAAO,SACjBF,UAAUG,KAAO,cACjBX,KAAKY,YAAYJ,WAEVA,WAuCPK,CAAyBb,MAAMc,MAAQjB,uGAQT,KAC9BZ,SAASkB,iBAAiB,SAAUC,QACnBA,EAAEE,OAAOL,QAAQ,mCAK9BG,EAAEW,uBACIC,IAAM,IAAIC,IAAIb,EAAEE,OAAOY,MAC7BF,IAAIG,aAAaC,IAAI,cAAevB,gBACpCL,OAAO6B,SAAWL,uCAOW,WAC3BA,IAAM,IAAIC,IAAIzB,OAAO6B,SAASH,UAC/BF,IAAIG,aAAaG,IAAI,4BAIpBC,eAAiBP,IAAIG,aAAaK,IAAI,eAItC1C,iBAAmBC,sBACzBD,iBAAiB2C,SAAS,EAAGF,gBAC7BtC,SAASkB,iBAAiB,oBAAoB,KAC1CrB,iBAAiB2C,SAAS,EAAGF"}
\ No newline at end of file
diff --git a/lib/amd/src/scroll_manager.js b/lib/amd/src/scroll_manager.js
new file mode 100644
index 00000000000..1ede365c934
--- /dev/null
+++ b/lib/amd/src/scroll_manager.js
@@ -0,0 +1,181 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+
+/**
+ * Scroll manager is a class that help with saving the scroll positing when you
+ * click on an action icon, and then when the page is reloaded after processing
+ * the action, it scrolls you to exactly where you were. This is much nicer for
+ * the user.
+ *
+ * To use this in your code, you need to ensure that:
+ * 1. The button that triggers the action has to have a click event handler that
+ * calls saveScrollPos()
+ * 2. After doing the processing, the redirect() function will add 'mdlscrollto'
+ * parameter into the redirect url automatically.
+ * 3. Finally, on the page that is reloaded (which should be the same as the one
+ * the user started on) you need to call scrollToSavedPosition()
+ * on page load.
+ *
+ * @module core/scroll_manager
+ * @copyright 2021 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/** @property {HTMLElement} scrollingElement the current scrolling element. */
+let scrollingElement = null;
+
+/**
+ * Is the element scrollable?
+ *
+ * @param {HTMLElement} element Element.
+ * @returns {boolean}
+ */
+const isScrollable = (element) => {
+ // Check if the element has scrollable content.
+ const hasScrollableContent = element.scrollHeight > element.clientHeight;
+
+ // If 'overflow-y' is set to hidden, the scroll bar is't show.
+ const elementOverflow = window.getComputedStyle(element).overflowY;
+ const isOverflowHidden = elementOverflow.indexOf('hidden') !== -1;
+
+ return hasScrollableContent && !isOverflowHidden;
+};
+
+/**
+ * Get the scrolling element.
+ *
+ * @returns {HTMLElement}
+ */
+const getScrollingElement = () => {
+ if (scrollingElement === null) {
+ const page = document.getElementById('page');
+ if (isScrollable(page)) {
+ scrollingElement = page;
+ } else {
+ scrollingElement = document.scrollingElement;
+ }
+ }
+
+ return scrollingElement;
+};
+
+/**
+ * Get current scroll position.
+ *
+ * @returns {Number} Scroll position.
+ */
+const getScrollPos = () => {
+ const scrollingElement = getScrollingElement();
+
+ return scrollingElement.scrollTop;
+};
+
+/**
+ * Get the scroll position for this form.
+ *
+ * @param {HTMLFormElement} form
+ * @returns {HTMLInputElement}
+ */
+const getScrollPositionElement = (form) => {
+ const element = form.querySelector('input[name=mdlscrollto]');
+ if (element) {
+ return element;
+ }
+
+ const scrollPos = document.createElement('input');
+ scrollPos.type = 'hidden';
+ scrollPos.name = 'mdlscrollto';
+ form.appendChild(scrollPos);
+
+ return scrollPos;
+};
+
+/**
+ * In the form that contains the element, set the value of the form field with
+ * name mdlscrollto to the current scroll position. If there is no element with
+ * that name, it creates a hidden form field with that name within the form.
+ *
+ * @param {string} elementId The element in the form.
+ */
+export const saveScrollPos = (elementId) => {
+ const element = document.getElementById(elementId);
+ const form = element.closest('form');
+ if (!form) {
+ return;
+ }
+
+ saveScrollPositionToForm(form);
+};
+
+/**
+ * Init event handlers for all links with data-savescrollposition=true.
+ * Set the value to the closest form.
+ */
+export const watchScrollButtonSaves = () => {
+ document.addEventListener('click', (e) => {
+ const button = e.target.closest('[data-savescrollposition="true"]');
+ if (button) {
+ saveScrollPositionToForm(button.form);
+ }
+ });
+};
+
+/**
+ * Save the position to form.
+ *
+ * @param {Object} form The form is saved scroll position.
+ */
+export const saveScrollPositionToForm = (form) => {
+ getScrollPositionElement(form).value = getScrollPos();
+};
+
+/**
+ * Init event handlers for all links with data-save-scroll=true.
+ * Handle to add mdlscrollto parameter to link using js when we click on the link.
+ *
+ */
+export const initLinksScrollPos = () => {
+ document.addEventListener('click', (e) => {
+ const link = e.target.closest('a[data-save-scroll=true]');
+ if (!link) {
+ return;
+ }
+
+ e.preventDefault();
+ const url = new URL(e.target.href);
+ url.searchParams.set('mdlscrollto', getScrollPos());
+ window.location = url;
+ });
+};
+
+/**
+ * If there is a parameter like mdlscrollto=123 in the URL, scroll to that saved position.
+ */
+export const scrollToSavedPosition = () => {
+ const url = new URL(window.location.href);
+ if (!url.searchParams.has('mdlscrollto')) {
+ return;
+ }
+
+ const scrollPosition = url.searchParams.get('mdlscrollto');
+
+ // Event onDOMReady is the effective one here. I am leaving the immediate call to
+ // window.scrollTo in case it reduces flicker.
+ const scrollingElement = getScrollingElement();
+ scrollingElement.scrollTo(0, scrollPosition);
+ document.addEventListener('DOMContentLoaded', () => {
+ scrollingElement.scrollTo(0, scrollPosition);
+ });
+};
diff --git a/mod/quiz/addrandom.php b/mod/quiz/addrandom.php
index 8d57c0d6149..d47d14a1892 100644
--- a/mod/quiz/addrandom.php
+++ b/mod/quiz/addrandom.php
@@ -39,7 +39,7 @@ list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) =
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
$addonpage = optional_param('addonpage', 0, PARAM_INT);
$category = optional_param('category', 0, PARAM_INT);
-$scrollpos = optional_param('scrollpos', 0, PARAM_INT);
+$mdlscrollto = optional_param('mdlscrollto', 0, PARAM_INT);
// Get the course object and related bits.
if (!$course = $DB->get_record('course', array('id' => $quiz->course))) {
@@ -59,8 +59,8 @@ if ($returnurl) {
} else {
$returnurl = new moodle_url('/mod/quiz/edit.php', array('cmid' => $cmid));
}
-if ($scrollpos) {
- $returnurl->param('scrollpos', $scrollpos);
+if ($mdlscrollto) {
+ $returnurl->param('mdlscrollto', $mdlscrollto);
}
$defaultcategoryobj = question_make_default_categories($contexts->all());
diff --git a/mod/quiz/classes/output/renderer.php b/mod/quiz/classes/output/renderer.php
index d5a06c8187d..25abdedfb99 100644
--- a/mod/quiz/classes/output/renderer.php
+++ b/mod/quiz/classes/output/renderer.php
@@ -563,8 +563,8 @@ class renderer extends plugin_renderer_base {
'value' => '0', 'id' => 'timeup']);
$output .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'sesskey',
'value' => sesskey()]);
- $output .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'scrollpos',
- 'value' => '', 'id' => 'scrollpos']);
+ $output .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => 'mdlscrollto',
+ 'value' => '', 'id' => 'mdlscrollto']);
// Add a hidden field with questionids. Do this at the end of the form, so
// if you navigate before the form has finished loading, it does not wipe all
@@ -622,9 +622,14 @@ class renderer extends plugin_renderer_base {
public function redo_question_button($slot, $disabled) {
$attributes = ['type' => 'submit', 'name' => 'redoslot' . $slot,
'value' => get_string('redoquestion', 'quiz'),
- 'class' => 'mod_quiz-redo_question_button btn btn-secondary'];
+ 'class' => 'mod_quiz-redo_question_button btn btn-secondary',
+ 'id' => 'redoslot' . $slot . '-submit',
+ 'data-savescrollposition' => 'true',
+ ];
if ($disabled) {
$attributes['disabled'] = 'disabled';
+ } else {
+ $this->page->requires->js_call_amd('core_question/question_engine', 'initSubmitButton', [$attributes['id']]);
}
return html_writer::div(html_writer::empty_tag('input', $attributes));
}
diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php
index 667289850c3..07d240aacbf 100644
--- a/mod/quiz/edit.php
+++ b/mod/quiz/edit.php
@@ -46,9 +46,7 @@ require_once(__DIR__ . '/../../config.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
require_once($CFG->dirroot . '/question/editlib.php');
-// These params are only passed from page request to request while we stay on
-// this page otherwise they would go in question_edit_setup.
-$scrollpos = optional_param('scrollpos', '', PARAM_INT);
+$mdlscrollto = optional_param('mdlscrollto', '', PARAM_INT);
list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) =
question_edit_setup('editq', '/mod/quiz/edit.php', true);
@@ -81,9 +79,6 @@ foreach ($params as $key => $value) {
}
$afteractionurl = new moodle_url($thispageurl);
-if ($scrollpos) {
- $afteractionurl->param('scrollpos', $scrollpos);
-}
if (optional_param('repaginate', false, PARAM_BOOL) && confirm_sesskey()) {
// Re-paginate the quiz.
@@ -94,6 +89,10 @@ if (optional_param('repaginate', false, PARAM_BOOL) && confirm_sesskey()) {
redirect($afteractionurl);
}
+if ($mdlscrollto) {
+ $afteractionurl->param('mdlscrollto', $mdlscrollto);
+}
+
if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sesskey()) {
// Add a single question to the current quiz.
$structure->check_can_be_edited();
diff --git a/mod/quiz/module.js b/mod/quiz/module.js
index b49d3dcf402..db7d3eda872 100644
--- a/mod/quiz/module.js
+++ b/mod/quiz/module.js
@@ -26,7 +26,9 @@
M.mod_quiz = M.mod_quiz || {};
M.mod_quiz.init_attempt_form = function(Y) {
- M.core_question_engine.init_form(Y, '#responseform');
+ require(['core_question/question_engine'], function(qEngine) {
+ qEngine.initForm('#responseform');
+ });
Y.on('submit', M.mod_quiz.timer.stop, '#responseform');
require(['core_form/changechecker'], function(FormChangeChecker) {
FormChangeChecker.watchFormById('responseform');
@@ -34,7 +36,9 @@ M.mod_quiz.init_attempt_form = function(Y) {
};
M.mod_quiz.init_review_form = function(Y) {
- M.core_question_engine.init_form(Y, '.questionflagsaveform');
+ require(['core_question/question_engine'], function(qEngine) {
+ qEngine.initForm('.questionflagsaveform');
+ });
Y.on('submit', function(e) { e.halt(); }, '.questionflagsaveform');
};
diff --git a/mod/quiz/processattempt.php b/mod/quiz/processattempt.php
index 4a871af17ce..bb2c2d4d933 100644
--- a/mod/quiz/processattempt.php
+++ b/mod/quiz/processattempt.php
@@ -45,7 +45,7 @@ $previous = optional_param('previous', false, PARAM_BOOL);
$next = optional_param('next', false, PARAM_BOOL);
$finishattempt = optional_param('finishattempt', false, PARAM_BOOL);
$timeup = optional_param('timeup', 0, PARAM_BOOL); // True if form was submitted by timer.
-$scrollpos = optional_param('scrollpos', '', PARAM_RAW);
+$mdlscrollto = optional_param('mdlscrollto', '', PARAM_RAW);
$cmid = optional_param('cmid', null, PARAM_INT);
$attemptobj = quiz_create_attempt_handling_errors($attemptid, $cmid);
@@ -62,8 +62,8 @@ if ($page == -1) {
$nexturl = $attemptobj->summary_url();
} else {
$nexturl = $attemptobj->attempt_url(null, $page);
- if ($scrollpos !== '') {
- $nexturl->param('scrollpos', $scrollpos);
+ if ($mdlscrollto !== '') {
+ $nexturl->param('mdlscrollto', $mdlscrollto);
}
}
diff --git a/question/amd/build/question_engine.min.js b/question/amd/build/question_engine.min.js
new file mode 100644
index 00000000000..95432d57f30
--- /dev/null
+++ b/question/amd/build/question_engine.min.js
@@ -0,0 +1,10 @@
+define("core_question/question_engine",["exports","core/scroll_manager","core_form/submit"],(function(_exports,scrollManager,formSubmit){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
+/**
+ * JavaScript required by the question engine.
+ *
+ * @module core_question/question_engine
+ * @copyright 2021 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.preventRepeatSubmission=_exports.initSubmitButton=_exports.initForm=void 0,scrollManager=_interopRequireWildcard(scrollManager),formSubmit=_interopRequireWildcard(formSubmit);_exports.initSubmitButton=button=>{formSubmit.init(button),scrollManager.watchScrollButtonSaves()};_exports.initForm=formSelector=>{const form=document.querySelector(formSelector);form.setAttribute("autocomplete","off"),form.addEventListener("submit",preventRepeatSubmission),form.addEventListener("key",(event=>{13===event.keyCode&&(event.target.matches("a")||event.target.matches('input[type="submit"]')||event.target.matches("input[type=img]")||event.target.matches("textarea")||event.target.matches("[contenteditable=true]")||event.preventDefault())}));[...form.querySelectorAll(".questionflagsavebutton")].forEach((node=>node.remove())),scrollManager.scrollToSavedPosition()};const preventRepeatSubmission=event=>{const form=event.target.closest("form");"1"!==form.dataset.formSubmitted?(setTimeout((()=>{[...form.querySelectorAll("input[type=submit]")].forEach((input=>input.setAttribute("disabled",!0)))})),form.dataset.formSubmitted="1"):event.preventDefault()};_exports.preventRepeatSubmission=preventRepeatSubmission}));
+
+//# sourceMappingURL=question_engine.min.js.map
\ No newline at end of file
diff --git a/question/amd/build/question_engine.min.js.map b/question/amd/build/question_engine.min.js.map
new file mode 100644
index 00000000000..4394c29d910
--- /dev/null
+++ b/question/amd/build/question_engine.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"question_engine.min.js","sources":["../src/question_engine.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * JavaScript required by the question engine.\n *\n * @module core_question/question_engine\n * @copyright 2021 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as scrollManager from 'core/scroll_manager';\nimport * as formSubmit from 'core_form/submit';\n\n/**\n * Initialise a question submit button. This saves the scroll position and\n * sets the fragment on the form submit URL so the page reloads in the right place.\n *\n * @param {string} button the id of the button in the HTML.\n */\nexport const initSubmitButton = button => {\n formSubmit.init(button);\n scrollManager.watchScrollButtonSaves();\n};\n\n/**\n * Initialise a form that contains questions printed using print_question.\n * This has the effect of:\n * 1. Turning off browser autocomlete.\n * 2. Stopping enter from submitting the form (or toggling the next flag) unless\n * keyboard focus is on the submit button or the flag.\n * 3. Removes any '.questionflagsavebutton's, since we have JavaScript to toggle\n * the flags using ajax.\n * 4. Scroll to the position indicated by scrollpos= in the URL, if it is there.\n * 5. Prevent the user from repeatedly submitting the form.\n *\n * @param {string} formSelector Selector to identify the form.\n */\nexport const initForm = (formSelector) => {\n const form = document.querySelector(formSelector);\n form.setAttribute('autocomplete', 'off');\n\n form.addEventListener('submit', preventRepeatSubmission);\n\n form.addEventListener('key', (event) => {\n if (event.keyCode !== 13) {\n return;\n }\n\n if (event.target.matches('a')) {\n return;\n }\n\n if (event.target.matches('input[type=\"submit\"]')) {\n return;\n }\n\n if (event.target.matches('input[type=img]')) {\n return;\n }\n\n if (event.target.matches('textarea') || event.target.matches('[contenteditable=true]')) {\n return;\n }\n\n event.preventDefault();\n });\n\n const questionFlagSaveButtons = form.querySelectorAll('.questionflagsavebutton');\n [...questionFlagSaveButtons].forEach((node) => node.remove());\n\n // Note: The scrollToSavedPosition function tries to wait until the content has loaded before firing.\n scrollManager.scrollToSavedPosition();\n};\n\n/**\n * Event handler to stop a question form being submitted more than once.\n *\n * @param {object} event the form submit event.\n */\nexport const preventRepeatSubmission = (event) => {\n const form = event.target.closest('form');\n if (form.dataset.formSubmitted === '1') {\n event.preventDefault();\n return;\n }\n\n setTimeout(() => {\n [...form.querySelectorAll('input[type=submit]')].forEach((input) => input.setAttribute('disabled', true));\n });\n form.dataset.formSubmitted = '1';\n};\n"],"names":["button","formSubmit","init","scrollManager","watchScrollButtonSaves","formSelector","form","document","querySelector","setAttribute","addEventListener","preventRepeatSubmission","event","keyCode","target","matches","preventDefault","querySelectorAll","forEach","node","remove","scrollToSavedPosition","closest","dataset","formSubmitted","setTimeout","input"],"mappings":";;;;;;;+QAgCgCA,SAC5BC,WAAWC,KAAKF,QAChBG,cAAcC,4CAgBOC,qBACfC,KAAOC,SAASC,cAAcH,cACpCC,KAAKG,aAAa,eAAgB,OAElCH,KAAKI,iBAAiB,SAAUC,yBAEhCL,KAAKI,iBAAiB,OAAQE,QACJ,KAAlBA,MAAMC,UAIND,MAAME,OAAOC,QAAQ,MAIrBH,MAAME,OAAOC,QAAQ,yBAIrBH,MAAME,OAAOC,QAAQ,oBAIrBH,MAAME,OAAOC,QAAQ,aAAeH,MAAME,OAAOC,QAAQ,2BAI7DH,MAAMI,yBAGsBV,KAAKW,iBAAiB,4BACzBC,SAASC,MAASA,KAAKC,WAGpDjB,cAAckB,+BAQLV,wBAA2BC,cAC9BN,KAAOM,MAAME,OAAOQ,QAAQ,QACC,MAA/BhB,KAAKiB,QAAQC,eAKjBC,YAAW,SACHnB,KAAKW,iBAAiB,uBAAuBC,SAASQ,OAAUA,MAAMjB,aAAa,YAAY,QAEvGH,KAAKiB,QAAQC,cAAgB,KAPzBZ,MAAMI"}
\ No newline at end of file
diff --git a/question/amd/src/question_engine.js b/question/amd/src/question_engine.js
new file mode 100644
index 00000000000..2e1c584610c
--- /dev/null
+++ b/question/amd/src/question_engine.js
@@ -0,0 +1,104 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+
+/**
+ * JavaScript required by the question engine.
+ *
+ * @module core_question/question_engine
+ * @copyright 2021 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import * as scrollManager from 'core/scroll_manager';
+import * as formSubmit from 'core_form/submit';
+
+/**
+ * Initialise a question submit button. This saves the scroll position and
+ * sets the fragment on the form submit URL so the page reloads in the right place.
+ *
+ * @param {string} button the id of the button in the HTML.
+ */
+export const initSubmitButton = button => {
+ formSubmit.init(button);
+ scrollManager.watchScrollButtonSaves();
+};
+
+/**
+ * Initialise a form that contains questions printed using print_question.
+ * This has the effect of:
+ * 1. Turning off browser autocomlete.
+ * 2. Stopping enter from submitting the form (or toggling the next flag) unless
+ * keyboard focus is on the submit button or the flag.
+ * 3. Removes any '.questionflagsavebutton's, since we have JavaScript to toggle
+ * the flags using ajax.
+ * 4. Scroll to the position indicated by scrollpos= in the URL, if it is there.
+ * 5. Prevent the user from repeatedly submitting the form.
+ *
+ * @param {string} formSelector Selector to identify the form.
+ */
+export const initForm = (formSelector) => {
+ const form = document.querySelector(formSelector);
+ form.setAttribute('autocomplete', 'off');
+
+ form.addEventListener('submit', preventRepeatSubmission);
+
+ form.addEventListener('key', (event) => {
+ if (event.keyCode !== 13) {
+ return;
+ }
+
+ if (event.target.matches('a')) {
+ return;
+ }
+
+ if (event.target.matches('input[type="submit"]')) {
+ return;
+ }
+
+ if (event.target.matches('input[type=img]')) {
+ return;
+ }
+
+ if (event.target.matches('textarea') || event.target.matches('[contenteditable=true]')) {
+ return;
+ }
+
+ event.preventDefault();
+ });
+
+ const questionFlagSaveButtons = form.querySelectorAll('.questionflagsavebutton');
+ [...questionFlagSaveButtons].forEach((node) => node.remove());
+
+ // Note: The scrollToSavedPosition function tries to wait until the content has loaded before firing.
+ scrollManager.scrollToSavedPosition();
+};
+
+/**
+ * Event handler to stop a question form being submitted more than once.
+ *
+ * @param {object} event the form submit event.
+ */
+export const preventRepeatSubmission = (event) => {
+ const form = event.target.closest('form');
+ if (form.dataset.formSubmitted === '1') {
+ event.preventDefault();
+ return;
+ }
+
+ setTimeout(() => {
+ [...form.querySelectorAll('input[type=submit]')].forEach((input) => input.setAttribute('disabled', true));
+ });
+ form.dataset.formSubmitted = '1';
+};
diff --git a/question/bank/editquestion/question.php b/question/bank/editquestion/question.php
index 72d9d8b27aa..c4b7479dfc5 100644
--- a/question/bank/editquestion/question.php
+++ b/question/bank/editquestion/question.php
@@ -37,7 +37,7 @@ $wizardnow = optional_param('wizardnow', '', PARAM_ALPHA);
$originalreturnurl = optional_param('returnurl', 0, PARAM_LOCALURL);
$appendqnumstring = optional_param('appendqnumstring', '', PARAM_ALPHA);
$inpopup = optional_param('inpopup', 0, PARAM_BOOL);
-$scrollpos = optional_param('scrollpos', 0, PARAM_INT);
+$mdlscrollto = optional_param('mdlscrollto', 0, PARAM_INT);
\core_question\local\bank\helper::require_plugin_enabled('qbank_editquestion');
@@ -72,8 +72,8 @@ if ($appendqnumstring !== '') {
if ($inpopup !== 0) {
$url->param('inpopup', $inpopup);
}
-if ($scrollpos) {
- $url->param('scrollpos', $scrollpos);
+if ($mdlscrollto) {
+ $url->param('mdlscrollto', $mdlscrollto);
}
$PAGE->set_url($url);
@@ -92,8 +92,8 @@ if ($originalreturnurl) {
} else {
$returnurl = $questionbankurl;
}
-if ($scrollpos) {
- $returnurl->param('scrollpos', $scrollpos);
+if ($mdlscrollto) {
+ $returnurl->param('mdlscrollto', $mdlscrollto);
}
if ($cmid) {
@@ -204,7 +204,7 @@ if ($wizardnow !== '') {
}
$toform = fullclone($question); // Send the question object and a few more parameters to the form.
$toform->category = "{$category->id},{$category->contextid}";
-$toform->scrollpos = $scrollpos;
+$toform->mdlscrollto = $mdlscrollto;
if ($formeditable && $id) {
$toform->categorymoveto = $toform->category;
}
@@ -332,10 +332,11 @@ if ($mform->is_cancelled()) {
}
} else {
- $nexturlparams = array(
+ $nexturlparams = [
'returnurl' => $originalreturnurl,
'appendqnumstring' => $appendqnumstring,
- 'scrollpos' => $scrollpos);
+ 'mdlscrollto' => $mdlscrollto,
+ ];
if (isset($fromform->nextpageparam) && is_array($fromform->nextpageparam)) {
// Useful for passing data to the next page which is not saved in the database.
$nexturlparams += $fromform->nextpageparam;
diff --git a/question/bank/previewquestion/amd/build/preview.min.js b/question/bank/previewquestion/amd/build/preview.min.js
index 05a84333044..663f7ba0810 100644
--- a/question/bank/previewquestion/amd/build/preview.min.js
+++ b/question/bank/previewquestion/amd/build/preview.min.js
@@ -1,11 +1,3 @@
-define("qbank_previewquestion/preview",["exports","jquery"],(function(_exports,_jquery){var obj;
-/**
- * Javascript for preview.
- *
- * @module qbank_preview/preview
- * @copyright 2021 Catalyst IT Australia Pty Ltd
- * @author Safat Shahin
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};_exports.init=(redirect,url)=>{if(!redirect){document.getElementById("close-previewquestion-page").onclick=()=>{null===window.opener?location.href=url:window.close()}}setupQuestionForm("responseform")};const setupQuestionForm=formElement=>{let form=document.getElementById(formElement);form&&(autocompleteOff(form),preventRepeatSubmission(form),removeClass(".questionflagsavebutton",form),scrollToSavedPos(form))},autocompleteOff=form=>{form.setAttribute("autocomplete","off")},preventRepeatSubmission=form=>{form.addEventListener("submit",(function(){return(0,_jquery.default)(this).submit((function(){return!1})),!0}))},removeClass=(classname,form)=>{form.querySelectorAll(classname).forEach((e=>e.remove()))},scrollToSavedPos=form=>{let matches=window.location.href.match(/^.*[?&]scrollpos=(\d*)(?:&|$|#).*$/,"$1");matches&&(window.scrollTo(0,matches[1]),form.addEventListener("DOMContentLoaded",(()=>{window.scrollTo(0,matches[1])})))}}));
+define("qbank_previewquestion/preview",["exports","core_question/question_engine"],(function(_exports,_question_engine){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;_exports.init=(redirect,url)=>{if(!redirect){document.getElementById("close-previewquestion-page").addEventListener("click",(e=>{e.preventDefault(),null===window.opener?location.href=url:window.close()}))}(0,_question_engine.initForm)("#responseform")}}));
//# sourceMappingURL=preview.min.js.map
\ No newline at end of file
diff --git a/question/bank/previewquestion/amd/build/preview.min.js.map b/question/bank/previewquestion/amd/build/preview.min.js.map
index c5ee224b374..40c460b3e0e 100644
--- a/question/bank/previewquestion/amd/build/preview.min.js.map
+++ b/question/bank/previewquestion/amd/build/preview.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"preview.min.js","sources":["../src/preview.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for preview.\n *\n * @module qbank_preview/preview\n * @copyright 2021 Catalyst IT Australia Pty Ltd\n * @author Safat Shahin \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\n\n/**\n * Set up the actions.\n *\n * @method init\n * @param {bool} redirect Redirect.\n * @param {string} url url to redirect.\n */\nexport const init = (redirect, url) => {\n if (!redirect) {\n let closeButton = document.getElementById('close-previewquestion-page');\n closeButton.onclick = () => {\n if (window.opener === null) {\n location.href = url;\n } else {\n window.close();\n }\n };\n }\n // Set up the form to be displayed.\n setupQuestionForm('responseform');\n};\n\n/**\n * Set up the form element to be displayed.\n *\n * @method setupQuestionForm\n * @param {string} formElement The form element.\n */\nconst setupQuestionForm = (formElement) => {\n let form = document.getElementById(formElement);\n if (form) {\n // Turning off browser autocomplete.\n autocompleteOff(form);\n // Stop a question form being submitted more than once.\n preventRepeatSubmission(form);\n // Removes any '.questionflagsavebutton's, since we have JavaScript to toggle.\n removeClass('.questionflagsavebutton', form);\n // Scroll to the position indicated by scrollpos= in the URL, if it is there.\n scrollToSavedPos(form);\n }\n};\n\n/**\n * Set the autocomplete off.\n *\n * @method autocompleteOff\n * @param {object} form The form element.\n */\nconst autocompleteOff = (form) => {\n form.setAttribute(\"autocomplete\", \"off\");\n};\n\n/**\n * Event handler to stop a question form being submitted more than once.\n *\n * @method preventRepeatSubmission\n * @param {object} form The form element.\n */\nconst preventRepeatSubmission = (form) => {\n form.addEventListener(\"submit\", function() {\n $(this).submit(function() {\n return false;\n });\n return true;\n });\n};\n\n/**\n * Removes a class inside an element.\n *\n * @method removeClass\n * @param {string} classname Class name.\n * @param {object} form The form element.\n */\nconst removeClass = (classname, form) => {\n form.querySelectorAll(classname).forEach(e => e.remove());\n};\n\n/**\n * If there is a parameter like scrollpos=123 in the URL, scroll to that saved position.\n * (Note: Moodle 4.0 and above do NOT support Internet Explorer 11 and below.)\n *\n * @method scrollToSavedPos\n * @param {object} form The form element.\n */\nconst scrollToSavedPos = (form) => {\n let matches = window.location.href.match(/^.*[?&]scrollpos=(\\d*)(?:&|$|#).*$/, '$1');\n if (matches) {\n // DOMContentLoaded is the effective one here. I am leaving the immediate call to\n // window.scrollTo in case it reduces flicker.\n window.scrollTo(0, matches[1]);\n form.addEventListener(\"DOMContentLoaded\", () => {\n window.scrollTo(0, matches[1]);\n });\n }\n};\n"],"names":["redirect","url","document","getElementById","onclick","window","opener","location","href","close","setupQuestionForm","formElement","form","autocompleteOff","preventRepeatSubmission","removeClass","scrollToSavedPos","setAttribute","addEventListener","this","submit","classname","querySelectorAll","forEach","e","remove","matches","match","scrollTo"],"mappings":";;;;;;;;wJAiCoB,CAACA,SAAUC,WACtBD,SAAU,CACOE,SAASC,eAAe,8BAC9BC,QAAU,KACI,OAAlBC,OAAOC,OACPC,SAASC,KAAOP,IAEhBI,OAAOI,SAKnBC,kBAAkB,uBAShBA,kBAAqBC,kBACnBC,KAAOV,SAASC,eAAeQ,aAC/BC,OAEAC,gBAAgBD,MAEhBE,wBAAwBF,MAExBG,YAAY,0BAA2BH,MAEvCI,iBAAiBJ,QAUnBC,gBAAmBD,OACrBA,KAAKK,aAAa,eAAgB,QAShCH,wBAA2BF,OAC7BA,KAAKM,iBAAiB,UAAU,qCAC1BC,MAAMC,QAAO,kBACJ,MAEJ,MAWTL,YAAc,CAACM,UAAWT,QAC5BA,KAAKU,iBAAiBD,WAAWE,SAAQC,GAAKA,EAAEC,YAU9CT,iBAAoBJ,WAClBc,QAAUrB,OAAOE,SAASC,KAAKmB,MAAM,qCAAsC,MAC3ED,UAGArB,OAAOuB,SAAS,EAAGF,QAAQ,IAC3Bd,KAAKM,iBAAiB,oBAAoB,KACtCb,OAAOuB,SAAS,EAAGF,QAAQ"}
\ No newline at end of file
+{"version":3,"file":"preview.min.js","sources":["../src/preview.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript for preview.\n *\n * @module qbank_preview/preview\n * @copyright 2021 Catalyst IT Australia Pty Ltd\n * @author Safat Shahin \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {initForm as initQuestionEngineForm} from 'core_question/question_engine';\n\n/**\n * Set up the actions.\n *\n * @method init\n * @param {bool} redirect Redirect.\n * @param {string} url url to redirect.\n */\nexport const init = (redirect, url) => {\n if (!redirect) {\n const closeButton = document.getElementById('close-previewquestion-page');\n closeButton.addEventListener('click', (e) => {\n e.preventDefault();\n if (window.opener === null) {\n location.href = url;\n } else {\n window.close();\n }\n });\n }\n // Set up the form to be displayed.\n initQuestionEngineForm('#responseform');\n};\n"],"names":["redirect","url","document","getElementById","addEventListener","e","preventDefault","window","opener","location","href","close"],"mappings":"mNAiCoB,CAACA,SAAUC,WACtBD,SAAU,CACSE,SAASC,eAAe,8BAChCC,iBAAiB,SAAUC,IACnCA,EAAEC,iBACoB,OAAlBC,OAAOC,OACPC,SAASC,KAAOT,IAEhBM,OAAOI,yCAKI"}
\ No newline at end of file
diff --git a/question/bank/previewquestion/amd/src/preview.js b/question/bank/previewquestion/amd/src/preview.js
index 446a4ee8d74..d1ef49c4cdd 100644
--- a/question/bank/previewquestion/amd/src/preview.js
+++ b/question/bank/previewquestion/amd/src/preview.js
@@ -22,7 +22,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-import $ from 'jquery';
+import {initForm as initQuestionEngineForm} from 'core_question/question_engine';
/**
* Set up the actions.
@@ -33,90 +33,16 @@ import $ from 'jquery';
*/
export const init = (redirect, url) => {
if (!redirect) {
- let closeButton = document.getElementById('close-previewquestion-page');
- closeButton.onclick = () => {
+ const closeButton = document.getElementById('close-previewquestion-page');
+ closeButton.addEventListener('click', (e) => {
+ e.preventDefault();
if (window.opener === null) {
location.href = url;
} else {
window.close();
}
- };
+ });
}
// Set up the form to be displayed.
- setupQuestionForm('responseform');
-};
-
-/**
- * Set up the form element to be displayed.
- *
- * @method setupQuestionForm
- * @param {string} formElement The form element.
- */
-const setupQuestionForm = (formElement) => {
- let form = document.getElementById(formElement);
- if (form) {
- // Turning off browser autocomplete.
- autocompleteOff(form);
- // Stop a question form being submitted more than once.
- preventRepeatSubmission(form);
- // Removes any '.questionflagsavebutton's, since we have JavaScript to toggle.
- removeClass('.questionflagsavebutton', form);
- // Scroll to the position indicated by scrollpos= in the URL, if it is there.
- scrollToSavedPos(form);
- }
-};
-
-/**
- * Set the autocomplete off.
- *
- * @method autocompleteOff
- * @param {object} form The form element.
- */
-const autocompleteOff = (form) => {
- form.setAttribute("autocomplete", "off");
-};
-
-/**
- * Event handler to stop a question form being submitted more than once.
- *
- * @method preventRepeatSubmission
- * @param {object} form The form element.
- */
-const preventRepeatSubmission = (form) => {
- form.addEventListener("submit", function() {
- $(this).submit(function() {
- return false;
- });
- return true;
- });
-};
-
-/**
- * Removes a class inside an element.
- *
- * @method removeClass
- * @param {string} classname Class name.
- * @param {object} form The form element.
- */
-const removeClass = (classname, form) => {
- form.querySelectorAll(classname).forEach(e => e.remove());
-};
-
-/**
- * If there is a parameter like scrollpos=123 in the URL, scroll to that saved position.
- * (Note: Moodle 4.0 and above do NOT support Internet Explorer 11 and below.)
- *
- * @method scrollToSavedPos
- * @param {object} form The form element.
- */
-const scrollToSavedPos = (form) => {
- let matches = window.location.href.match(/^.*[?&]scrollpos=(\d*)(?:&|$|#).*$/, '$1');
- if (matches) {
- // DOMContentLoaded is the effective one here. I am leaving the immediate call to
- // window.scrollTo in case it reduces flicker.
- window.scrollTo(0, matches[1]);
- form.addEventListener("DOMContentLoaded", () => {
- window.scrollTo(0, matches[1]);
- });
- }
+ initQuestionEngineForm('#responseform');
};
diff --git a/question/bank/previewquestion/preview.php b/question/bank/previewquestion/preview.php
index b89616fdb8c..a21585c5aad 100644
--- a/question/bank/previewquestion/preview.php
+++ b/question/bank/previewquestion/preview.php
@@ -190,9 +190,9 @@ if (data_submitted() && confirm_sesskey()) {
question_engine::save_questions_usage_by_activity($quba);
$transaction->allow_commit();
- $scrollpos = optional_param('scrollpos', '', PARAM_RAW);
- if ($scrollpos !== '') {
- $actionurl->param('scrollpos', (int) $scrollpos);
+ $mdlscrollto = optional_param('mdlscrollto', '', PARAM_RAW);
+ if ($mdlscrollto !== '') {
+ $actionurl->param('mdlscrollto', (int) $mdlscrollto);
}
redirect($actionurl);
}
diff --git a/question/behaviour/interactive/renderer.php b/question/behaviour/interactive/renderer.php
index 804a9a36c7f..7a12294d559 100644
--- a/question/behaviour/interactive/renderer.php
+++ b/question/behaviour/interactive/renderer.php
@@ -52,21 +52,21 @@ class qbehaviour_interactive_renderer extends qbehaviour_renderer {
return '';
}
- $attributes = array(
+ $attributes = [
'type' => 'submit',
'id' => $qa->get_behaviour_field_name('tryagain'),
'name' => $qa->get_behaviour_field_name('tryagain'),
'value' => get_string('tryagain', 'qbehaviour_interactive'),
'class' => 'submit btn btn-secondary',
- );
+ 'data-savescrollposition' => 'true',
+ ];
if ($options->readonly === qbehaviour_interactive::TRY_AGAIN_VISIBLE_READONLY) {
// This means the question really was rendered with read-only option.
$attributes['disabled'] = 'disabled';
}
$output = html_writer::empty_tag('input', $attributes);
if (empty($attributes['disabled'])) {
- $this->page->requires->js_init_call('M.core_question_engine.init_submit_button',
- array($attributes['id']));
+ $this->page->requires->js_call_amd('core_question/question_engine', 'initSubmitButton', [$attributes['id']]);
}
return $output;
}
diff --git a/question/behaviour/rendererbase.php b/question/behaviour/rendererbase.php
index db5ff9ebbc6..983b7ce53c6 100644
--- a/question/behaviour/rendererbase.php
+++ b/question/behaviour/rendererbase.php
@@ -236,14 +236,14 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
'name' => $qa->get_behaviour_field_name('submit'),
'value' => get_string('check', 'question'),
'class' => 'submit btn btn-secondary',
+ 'data-savescrollposition' => 'true',
);
if ($options->readonly) {
$attributes['disabled'] = 'disabled';
}
$output = html_writer::empty_tag('input', $attributes);
if (!$options->readonly) {
- $this->page->requires->js_init_call('M.core_question_engine.init_submit_button',
- array($attributes['id']));
+ $this->page->requires->js_call_amd('core_question/question_engine', 'initSubmitButton', [$attributes['id']]);
}
return $output;
}
diff --git a/question/qengine.js b/question/qengine.js
index 6a89679e730..f5f120f119e 100644
--- a/question/qengine.js
+++ b/question/qengine.js
@@ -19,105 +19,87 @@
* @package moodlecore
* @subpackage questionengine
* @copyright 2008 The Open University
+ * @deprecated since Moodle 4.0
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-/**
- * Scroll manager is a class that help with saving the scroll positing when you
- * click on an action icon, and then when the page is reloaded after processing
- * the action, it scrolls you to exactly where you were. This is much nicer for
- * the user.
- *
- * To use this in your code, you need to ensure that:
- * 1. The button that triggers the action has to have a click event handler that
- * calls M.core_scroll_manager.save_scroll_pos
- * 2. The script that process the action has to grab the scrollpos parameter
- * using $scrollpos = optional_param('scrollpos', 0, PARAM_INT);
- * 3. After doing the processing, it must add ->param('scrollpos', $scrollpos)
- * to the URL that it redirects to.
- * 4. Finally, on the page that is reloaded (which should be the same as the one
- * the user started on) you need to call M.core_scroll_manager.scroll_to_saved_pos
- * on page load.
- */
M.core_scroll_manager = M.core_scroll_manager || {};
-/**
- * In the form that contains the element, set the value of the form field with
- * name scrollpos to the current scroll position. If there is no element with
- * that name, it creates a hidden form field wiht that name within the form.
- * @param element the element in the form. Should be something that can be
- * passed to Y.one.
- */
-M.core_scroll_manager.save_scroll_pos = function(Y, element) {
- if (typeof(element) == 'string') {
- // Have to use getElementById here because element id can contain :.
- element = Y.one(document.getElementById(element));
- }
- var form = element.ancestor('form');
- if (!form) {
- return;
- }
- var scrollpos = form.one('input[name=scrollpos]');
- if (!scrollpos) {
- scrollpos = form.appendChild(form.create(''));
- }
- scrollpos.set('value', form.get('docScrollY'));
-}
+// TODO Remove the scroll manager and deprecation layer in 4.6 MDL-76685.
+/* eslint-disable */
+var loadedPromise = new Promise(function(resolve) {
+ require(['core/scroll_manager'], function(ScrollManager) {
+ var transitionLayer = {};
-/**
- * Event handler that can be used on a link. Assumes that the link already
- * contains at least one URL parameter.
- */
-M.core_scroll_manager.save_scroll_action = function(e) {
- var link = e.target.ancestor('a[href]');
- if (!link) {
- M.core_scroll_manager.save_scroll_pos({}, e.target);
- return;
- }
- link.set('href', link.get('href') + '&scrollpos=' + link.get('docScrollY'));
-}
+ var deprecatedNotice = function(functionName, newFunctionName) {
+ window.console.error(
+ "The " + functionName + " function has been deprecated. " +
+ "Please use core/scroll_manager::" + newFunctionName + "() instead"
+ );
+ };
-/**
- * If there is a parameter like scrollpos=123 in the URL, scroll to that saved position.
- * @deprecated since Moodle 4.0
- * @see question\bank\qbank_previewquestion\amd\src
- * @todo Final deprecation on Moodle 4.4 MDL-72438
- */
-M.core_scroll_manager.scroll_to_saved_pos = function(Y) {
- Y.log("The scroll_to_saved_pos function has been deprecated. " +
- "Please use scrollToSavedPos() in qbank_preview/preview.js instead.", 'moodle-core-notification', 'warn');
+ transitionLayer.save_scroll_pos = function(Y, element) {
+ deprecatedNotice('save_scroll_pos', 'saveScrollPos');
+ ScrollManager.saveScrollPos(element);
+ };
- var matches = window.location.href.match(/^.*[?&]scrollpos=(\d*)(?:&|$|#).*$/, '$1');
- if (matches) {
- // onDOMReady is the effective one here. I am leaving the immediate call to
- // window.scrollTo in case it reduces flicker.
- window.scrollTo(0, matches[1]);
- Y.on('domready', function() { window.scrollTo(0, matches[1]); });
+ transitionLayer.scroll_to_saved_pos = function() {
+ deprecatedNotice('scroll_to_saved_pos', 'scrollToSavedPosition');
+ ScrollManager.scrollToSavedPosition();
+ };
- // And the following horror is necessary to make it work in IE 8.
- // Note that the class ie8 on body is only there in Moodle 2.0 and OU Moodle.
- if (Y.one('body').hasClass('ie')) {
- M.core_scroll_manager.force_ie_to_scroll(Y, matches[1])
- }
- }
-}
+ M.core_scroll_manager = transitionLayer;
-/**
- * Beat IE into submission.
- * @param targetpos the target scroll position.
- */
-M.core_scroll_manager.force_ie_to_scroll = function(Y, targetpos) {
- var hackcount = 25;
- function do_scroll() {
- window.scrollTo(0, targetpos);
- hackcount -= 1;
- if (hackcount > 0) {
- setTimeout(do_scroll, 10);
- }
- }
- Y.on('load', do_scroll, window);
+ resolve(transitionLayer);
+ });
+});
+
+var callPromisedFunction = function(functionName, args) {
+ loadedPromise.then(function(transitionLayer) {
+ transitionLayer[functionName].apply(null, args);
+ });
+};
+
+if (!M.core_scroll_manager.save_scroll_pos) {
+ // Note: This object is short lived.
+ // It only lives until the new scroll manager is loaded, at which point it is replaced.
+
+ /**
+ * In the form that contains the element, set the value of the form field with
+ * name scrollpos to the current scroll position. If there is no element with
+ * that name, it creates a hidden form field with that name within the form.
+ * @deprecated since Moodle 4.0
+ * @see core/scroll_manager
+ * @param element the element in the form. Should be something that can be
+ * passed to Y.one.
+ */
+ M.core_scroll_manager.save_scroll_pos = function(Y, element) {
+ callPromisedFunction(M.core_scroll_manager.save_scroll_pos, [Y, element]);
+ };
+
+ /**
+ * Event handler that can be used on a link. Assumes that the link already
+ * contains at least one URL parameter.
+ * @deprecated since Moodle 4.0
+ * @see core/scroll_manager
+ */
+ M.core_scroll_manager.save_scroll_action = function() {
+ Y.log("The scroll_to_saved_pos function has been deprecated. " +
+ "Please use initLinksScrollPos in core/scroll_manager instead.", 'moodle-core-notification', 'warn');
+ };
+
+ /**
+ * If there is a parameter like scrollpos=123 in the URL, scroll to that saved position.
+ * @deprecated since Moodle 4.0
+ * @see core/scroll_manager
+ * @todo Final deprecation on Moodle 4.4 MDL-72438
+ */
+ M.core_scroll_manager.scroll_to_saved_pos = function(Y) {
+ callPromisedFunction(M.core_scroll_manager.scroll_to_saved_pos, Y);
+ };
}
+/* eslint-enable */
M.core_question_engine = M.core_question_engine || {};
@@ -129,9 +111,14 @@ M.core_question_engine.questionformalreadysubmitted = false;
/**
* Initialise a question submit button. This saves the scroll position and
* sets the fragment on the form submit URL so the page reloads in the right place.
+ * @deprecated since Moodle 4.0
+ * @see core_question/question_engine
* @param button the id of the button in the HTML.
*/
M.core_question_engine.init_submit_button = function(Y, button) {
+ Y.log("The core_question_engine.init_submit_button function has been deprecated. " +
+ "Please use initSubmitButton in core_question/question_engine instead.", 'moodle-core-notification', 'warn');
+
require(['core_form/submit'], function(submit) {
submit.init(button);
});
@@ -160,12 +147,12 @@ M.core_question_engine.init_submit_button = function(Y, button) {
* @param Y the Yahoo object. Needs to have the DOM and Event modules loaded.
* @param form something that can be passed to Y.one, to find the form element.
* @deprecated since Moodle 4.0
- * @see question\bank\qbank_previewquestion\amd\src
+ * @see core_question/question_engine
* @todo Final deprecation on Moodle 4.4 MDL-72438
*/
M.core_question_engine.init_form = function(Y, form) {
Y.log("The core_question_engine.init_form function has been deprecated. " +
- "Please use setupQuestionForm() in qbank_preview/preview.js instead.", 'moodle-core-notification', 'warn');
+ "Please use init_form in core_question/question_engine instead.", 'moodle-core-notification', 'warn');
Y.one(form).setAttribute('autocomplete', 'off');
@@ -188,12 +175,12 @@ M.core_question_engine.init_form = function(Y, form) {
* @param e the form submit event.
* @param form the form element.
* @deprecated since Moodle 4.0
- * @see question\bank\qbank_previewquestion\amd\src
+ * @see core_question/question_engine
* @todo Final deprecation on Moodle 4.4 MDL-72438
*/
M.core_question_engine.prevent_repeat_submission = function(e, Y) {
Y.log("The prevent_repeat_submission function has been deprecated. " +
- "Please use preventRepeatSubmission in qbank_preview/preview.js instead.", 'moodle-core-notification', 'warn');
+ "Please use preventRepeatSubmission in core_question/question_engine instead.", 'moodle-core-notification', 'warn');
if (M.core_question_engine.questionformalreadysubmitted) {
e.halt();
diff --git a/question/type/edit_question_form.php b/question/type/edit_question_form.php
index c684030562a..4fe7ed56fac 100644
--- a/question/type/edit_question_form.php
+++ b/question/type/edit_question_form.php
@@ -52,8 +52,8 @@ abstract class question_wizard_form extends moodleform {
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_LOCALURL);
- $mform->addElement('hidden', 'scrollpos');
- $mform->setType('scrollpos', PARAM_INT);
+ $mform->addElement('hidden', 'mdlscrollto');
+ $mform->setType('mdlscrollto', PARAM_INT);
$mform->addElement('hidden', 'appendqnumstring');
$mform->setType('appendqnumstring', PARAM_ALPHA);
diff --git a/question/upgrade.txt b/question/upgrade.txt
index d477f1a797c..9881c3d79f1 100644
--- a/question/upgrade.txt
+++ b/question/upgrade.txt
@@ -1,5 +1,10 @@
This files describes API changes for code that uses the question API.
+=== 4.2 ===
+
+1) The question/qengine.js has been deprecated. We create core_question/question_engine
+ and core/scroll_manager to replace it.
+
=== 4.1 ===
1) get_bulk_action_key() in core_question\local\bank\bulk_action_base class is deprecated and renamed to get_key().