182 lines
5.5 KiB
JavaScript
182 lines
5.5 KiB
JavaScript
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* 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);
|
|
});
|
|
};
|