From fe99a6fb0207a4b295bf93eb0155e9e3f0ae00e9 Mon Sep 17 00:00:00 2001 From: Andre Menrath Date: Mon, 26 Aug 2024 11:05:53 +0200 Subject: [PATCH] issue-119: make the back-to-top JS vanilla ES6 (jQuery free) --- CHANGES.md | 7 +- amd/build/backtotopbutton.min.js | 11 +- amd/build/backtotopbutton.min.js.map | 2 +- amd/src/backtotopbutton.js | 161 +++++++++++++-------------- 4 files changed, 81 insertions(+), 100 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5a1f56a06df..8930c296ba8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Changes ### Unreleased +* 2024-08-26 - Improvement: Make back-to-top button jQuery-free. * 2024-08-24 - Upgrade: Update Bootstrap classes for Moodle 4.4. * 2024-08-11 - Updated Moodle Plugin CI to latest upstream recommendations * 2024-07-24 - Test: Fix broken Behat scenario 'Suppress 'Chat to course participants' link', resolves #696 @@ -23,7 +24,7 @@ Changes * 2024-07-04 - Upgrade: Fix Behat tests which broke due to the introduction of section pages in Moodle core. * 2024-07-04 - Upgrade: Adopt changes in boostnavbar.php from Boost core. * 2024-07-04 - Upgrade: Fix Behat tests which broke due to changes in the section naming in Moodle core. -* 2024-07-04 - Upgrade: Adapt a Behat test as planned regarding the new theme selector in Moodle core. +* 2024-07-04 - Upgrade: Adapt a Behat test as planned regarding the new theme selector in Moodle core. * 2024-07-04 - Upgrade: Fix Behat tests which broke due to changes on the MyCourses page in Moodle core. * 2024-06-25 - Upgrade: Adopt and handle core changes for the footersuppressstandardfooter_* settings, moving from callback functions to hooks. * 2024-06-19 - Upgrade: Adopt changes in event-list-item.mustache from block_timeline in core. @@ -32,7 +33,7 @@ Changes * 2024-06-19 - Upgrade: Adopt changes in navbar.mustache from Boost core. * 2024-06-01 - Prepare compatibility for Moodle 4.4. -### v4.3-r15 +### v4.3-r15 * 2024-07-11 - Bugfix: Allow external SCSS to use SCSS variables by disabling the SCSS validation, resolves #683. * 2024-06-23 - Upstream change: Adopt change in view-chards.mustache from MDL-70829. @@ -40,7 +41,7 @@ Changes * 2024-06-13 - Cleanup: Change @codingStandardsIgnore tags to phpcs:disable, resolves #676. * 2024-06-12 - Cleanup: Fix CSS warnings in external SCSS tests, resolves #674. -### v4.3-r14 +### v4.3-r14 * 2024-06-10 - Cleanup: Introduce a dedicated Behat step to deactivate and activate debugging, resolves #670. * 2024-05-05 - Cleanup: Fix 'Implicitly marking a parameter as nullable is deprecated since PHP 8.4' codechecker warning, resolves #667. diff --git a/amd/build/backtotopbutton.min.js b/amd/build/backtotopbutton.min.js index ee2a5e91c15..09433f9f856 100644 --- a/amd/build/backtotopbutton.min.js +++ b/amd/build/backtotopbutton.min.js @@ -1,12 +1,3 @@ -/** - * Theme Boost Union - JS code back to top button - * - * @module theme_boost_union/backtotopbutton - * @copyright 2022 Alexander Bias, lern.link GmbH - * @copyright on behalf of Zurich University of Applied Sciences (ZHAW) - * @copyright based on code from theme_boost_campus by Kathrin Osswald. - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("theme_boost_union/backtotopbutton",["jquery","core/str","core/notification"],(function($,str,Notification){let buttonShown=!1;function checkAndHide(){!0===buttonShown&&$("#back-to-top").fadeOut(100,(function(){buttonShown=!1}))}function checkAndShow(){!1===buttonShown&&$("#back-to-top").fadeIn(300,(function(){buttonShown=!0}))}return{init:function(){!function(){let stringsPromise=str.get_string("backtotop","theme_boost_union");$.when(stringsPromise).then((function(string){return $("#page-footer").after(''),$(window).scrollTop()>220?checkAndShow():checkAndHide(),$(window).on("scroll",(function(){$(window).scrollTop()>220?checkAndShow():checkAndHide()})),$("#back-to-top").on("click",(function(event){event.preventDefault(),$("html, body").animate({scrollTop:0},500),$("#back-to-top").blur()})),$("#page-footer .btn-footer-communication").length&&$("body").addClass("theme-boost-union-commincourse"),!0})).fail(Notification.exception)}()}}})); +define("theme_boost_union/backtotopbutton",["exports","core/str"],(function(_exports,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;const scrollToTop=event=>{event.preventDefault(),window.scrollTo({top:0,left:0,behavior:"smooth"})};_exports.init=async()=>{const backToTopString=await(0,_str.getString)("backtotop","theme_boost_union"),footer=document.querySelector("#page-footer"),button=(backToTopString=>{let button=document.createElement("button");return button.id="back-to-top",button.className="btn btn-icon bg-secondary icon-no-margin d-print-none",button.setAttribute("aria-label",backToTopString),button.innerHTML='',button})(backToTopString);footer.after(button);const throttledScrollHandler=((func,limit)=>{let inThrottle=!1;return function(){if(!inThrottle){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];func.apply(void 0,args),inThrottle=!0,setTimeout((()=>{inThrottle=!1}),limit)}}})((()=>((button,scrollDistance)=>{button.style.display=document.documentElement.scrollTop>scrollDistance?"block":"none"})(button,220)),200);document.addEventListener("scroll",throttledScrollHandler),button.addEventListener("click",scrollToTop)}})); //# sourceMappingURL=backtotopbutton.min.js.map \ No newline at end of file diff --git a/amd/build/backtotopbutton.min.js.map b/amd/build/backtotopbutton.min.js.map index 01dea03d1c9..c9f3f04641f 100644 --- a/amd/build/backtotopbutton.min.js.map +++ b/amd/build/backtotopbutton.min.js.map @@ -1 +1 @@ -{"version":3,"file":"backtotopbutton.min.js","sources":["../src/backtotopbutton.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 * Theme Boost Union - JS code back to top button\n *\n * @module theme_boost_union/backtotopbutton\n * @copyright 2022 Alexander Bias, lern.link GmbH \n * @copyright on behalf of Zurich University of Applied Sciences (ZHAW)\n * @copyright based on code from theme_boost_campus by Kathrin Osswald.\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/str', 'core/notification'], function($, str, Notification) {\n \"use strict\";\n\n // Remember if the back to top button is shown currently.\n let buttonShown = false;\n\n /**\n * Initializing.\n */\n function initBackToTop() {\n // Define the scroll distance after which the button will be shown.\n const scrolldistance = 220;\n\n // Get the string backtotop from language file.\n let stringsPromise = str.get_string('backtotop', 'theme_boost_union');\n\n // If the string has arrived, add backtotop button to DOM and add scroll and click handlers.\n $.when(stringsPromise).then(function(string) {\n // Add a fontawesome icon after the footer as the back to top button.\n $('#page-footer').after('');\n\n // Check directly if the button should be shown.\n // This is helpful for all cases when this code here runs _after_ the page has been scrolled,\n // especially by the scrollspy feature or by a simple browser page reload.\n if ($(window).scrollTop() > scrolldistance) {\n checkAndShow();\n } else {\n checkAndHide();\n }\n\n // This function fades the button in when the page is scrolled down or fades it out\n // if the user is at the top of the page again.\n $(window).on('scroll', function() {\n if ($(window).scrollTop() > scrolldistance) {\n checkAndShow();\n } else {\n checkAndHide();\n }\n });\n\n // This function scrolls the page to top with a duration of 500ms.\n $('#back-to-top').on('click', function(event) {\n event.preventDefault();\n $('html, body').animate({scrollTop: 0}, 500);\n $('#back-to-top').blur();\n });\n\n // This will check if there is a communication button shown on the page already.\n // If yes, it will add a class to the body tag which will be later used to align the back-to-top button\n // with the communications button.\n // This is necessary as the communications button would otherwise be overlaid by the back-to-top button.\n if ($('#page-footer .btn-footer-communication').length) {\n $('body').addClass('theme-boost-union-commincourse');\n }\n\n return true;\n }).fail(Notification.exception);\n }\n\n /**\n * Helper function to handle the button visibility when the page is scrolling up.\n */\n function checkAndHide() {\n // Check if the button is still shown.\n if (buttonShown === true) {\n // Fade it out and remember the status in the end.\n // To be precise, the faceOut() function will be called multiple times as buttonShown is not set until the button is\n // really faded out. However, as soon as it is faded out, it won't be called until the button is shown again.\n $('#back-to-top').fadeOut(100, function() {\n buttonShown = false;\n });\n }\n }\n\n /**\n * Helper function to handle the button visibility when the page is scrolling down.\n */\n function checkAndShow() {\n // Check if the button is not yet shown.\n if (buttonShown === false) {\n // Fade it in and remember the status in the end.\n $('#back-to-top').fadeIn(300, function() {\n buttonShown = true;\n });\n }\n }\n\n return {\n init: function() {\n initBackToTop();\n }\n };\n});\n"],"names":["define","$","str","Notification","buttonShown","checkAndHide","fadeOut","checkAndShow","fadeIn","init","stringsPromise","get_string","when","then","string","after","window","scrollTop","on","event","preventDefault","animate","blur","length","addClass","fail","exception","initBackToTop"],"mappings":";;;;;;;;;AAyBAA,2CAAO,CAAC,SAAU,WAAY,sBAAsB,SAASC,EAAGC,IAAKC,kBAI7DC,aAAc,WA6DTC,gBAEe,IAAhBD,aAIAH,EAAE,gBAAgBK,QAAQ,KAAK,WAC3BF,aAAc,cAQjBG,gBAEe,IAAhBH,aAEAH,EAAE,gBAAgBO,OAAO,KAAK,WAC1BJ,aAAc,WAKnB,CACHK,KAAM,2BA7EFC,eAAiBR,IAAIS,WAAW,YAAa,qBAGjDV,EAAEW,KAAKF,gBAAgBG,MAAK,SAASC,eAEjCb,EAAE,gBAAgBc,MAAM,qGAECD,OAFD,yEAQpBb,EAAEe,QAAQC,YAhBK,IAiBfV,eAEAF,eAKJJ,EAAEe,QAAQE,GAAG,UAAU,WACfjB,EAAEe,QAAQC,YAzBC,IA0BXV,eAEAF,kBAKRJ,EAAE,gBAAgBiB,GAAG,SAAS,SAASC,OACnCA,MAAMC,iBACNnB,EAAE,cAAcoB,QAAQ,CAACJ,UAAW,GAAI,KACxChB,EAAE,gBAAgBqB,UAOlBrB,EAAE,0CAA0CsB,QAC5CtB,EAAE,QAAQuB,SAAS,mCAGhB,KACRC,KAAKtB,aAAauB,WAiCjBC"} \ No newline at end of file +{"version":3,"file":"backtotopbutton.min.js","sources":["../src/backtotopbutton.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 * Theme Boost Union - JS code back to top button\n *\n * @module theme_boost_union/backtotopbutton\n * @copyright 2022 Alexander Bias, lern.link GmbH \n * @copyright on behalf of Zurich University of Applied Sciences (ZHAW)\n * @copyright based on code from theme_boost_campus by Kathrin Osswald.\n * @copyright 2024 University of Graz based on code/ideas of Mark Sharp \n * written 2022 for Solent University {@link https://www.solent.ac.uk}\n * @author André Menrath \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getString} from 'core/str';\n\n/**\n * Create the back-to-top button element.\n *\n * @param {string} backToTopString Aria text for the back-to-top button.\n */\nconst createBackToTopButton = (backToTopString) => {\n let button = document.createElement('button');\n button.id = 'back-to-top';\n button.className = 'btn btn-icon bg-secondary icon-no-margin d-print-none';\n button.setAttribute('aria-label', backToTopString);\n button.innerHTML = '';\n return button;\n};\n\n/**\n * Scroll event handler.\n * @param {element} button The back-to-top button.\n * @param {integer} scrollDistance Scroll distance from the top when to show the back-to-top button.\n */\nconst handleScroll = (button, scrollDistance) => {\n button.style.display = document.documentElement.scrollTop > scrollDistance ? 'block' : 'none';\n};\n\n/**\n * Scroll to top behavior.\n *\n * @param {event} event\n */\nconst scrollToTop = (event) => {\n event.preventDefault();\n window.scrollTo({\n top: 0,\n left: 0,\n behavior: 'smooth'\n });\n};\n\n/**\n * Throttle function to limit calls.\n *\n * @param {function} func The function to throttle.\n * @param {number} limit The time interval in milliseconds to throttle the function.\n *\n * @returns {function} The throttled function.\n */\nconst throttle = (func, limit) => {\n let inThrottle = false;\n\n return (...args) => {\n if (!inThrottle) {\n func.apply(this, args);\n inThrottle = true;\n setTimeout(() => {\n inThrottle = false;\n }, limit);\n }\n };\n};\n\n/**\n * Initial setup for the back to top button.\n */\nexport const init = async() => {\n // Configuration value when to start showing the back-to-top button.\n const scrollDistance = 220;\n\n // Aria text used for the back-to-top button.\n const backToTopString = await getString('backtotop', 'theme_boost_union');\n\n // Create and add the back-to-top button to the DOM.\n const footer = document.querySelector('#page-footer');\n const button = createBackToTopButton(backToTopString);\n footer.after(button);\n\n // Add event listeners that toggle the visibility and the behavior of the back-to-top button.\n const throttledScrollHandler = throttle(() => handleScroll(button, scrollDistance), 200);\n\n document.addEventListener('scroll', throttledScrollHandler);\n button.addEventListener('click', scrollToTop);\n};\n"],"names":["scrollToTop","event","preventDefault","window","scrollTo","top","left","behavior","async","backToTopString","footer","document","querySelector","button","createElement","id","className","setAttribute","innerHTML","createBackToTopButton","after","throttledScrollHandler","func","limit","inThrottle","args","apply","setTimeout","throttle","scrollDistance","style","display","documentElement","scrollTop","handleScroll","addEventListener"],"mappings":"8KA0DMA,YAAeC,QACjBA,MAAMC,iBACNC,OAAOC,SAAS,CACZC,IAAK,EACLC,KAAM,EACNC,SAAU,0BA6BEC,gBAKVC,sBAAwB,kBAAU,YAAa,qBAG/CC,OAASC,SAASC,cAAc,gBAChCC,OAlEqBJ,CAAAA,sBACvBI,OAASF,SAASG,cAAc,iBACpCD,OAAOE,GAAK,cACZF,OAAOG,UAAY,wDACnBH,OAAOI,aAAa,aAAcR,iBAClCI,OAAOK,UAAY,4DACZL,QA4DQM,CAAsBV,iBACrCC,OAAOU,MAAMP,cAGPQ,uBA9BO,EAACC,KAAMC,aAChBC,YAAa,SAEV,eACEA,WAAY,+BADVC,6CAAAA,2BAEHH,KAAKI,aAAYD,MACjBD,YAAa,EACbG,YAAW,KACPH,YAAa,IACdD,UAqBoBK,EAAS,IAxDvB,EAACf,OAAQgB,kBAC1BhB,OAAOiB,MAAMC,QAAUpB,SAASqB,gBAAgBC,UAAYJ,eAAiB,QAAU,QAuDzCK,CAAarB,OAXpC,MAW6D,KAEpFF,SAASwB,iBAAiB,SAAUd,wBACpCR,OAAOsB,iBAAiB,QAASnC"} \ No newline at end of file diff --git a/amd/src/backtotopbutton.js b/amd/src/backtotopbutton.js index fdd51559e04..2ce8a90a641 100644 --- a/amd/src/backtotopbutton.js +++ b/amd/src/backtotopbutton.js @@ -20,102 +20,91 @@ * @copyright 2022 Alexander Bias, lern.link GmbH * @copyright on behalf of Zurich University of Applied Sciences (ZHAW) * @copyright based on code from theme_boost_campus by Kathrin Osswald. + * @copyright 2024 University of Graz based on code/ideas of Mark Sharp + * written 2022 for Solent University {@link https://www.solent.ac.uk} + * @author André Menrath * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/str', 'core/notification'], function($, str, Notification) { - "use strict"; +import {getString} from 'core/str'; - // Remember if the back to top button is shown currently. - let buttonShown = false; - - /** - * Initializing. - */ - function initBackToTop() { - // Define the scroll distance after which the button will be shown. - const scrolldistance = 220; - - // Get the string backtotop from language file. - let stringsPromise = str.get_string('backtotop', 'theme_boost_union'); +/** + * Create the back-to-top button element. + * + * @param {string} backToTopString Aria text for the back-to-top button. + */ +const createBackToTopButton = (backToTopString) => { + let button = document.createElement('button'); + button.id = 'back-to-top'; + button.className = 'btn btn-icon bg-secondary icon-no-margin d-print-none'; + button.setAttribute('aria-label', backToTopString); + button.innerHTML = ''; + return button; +}; - // If the string has arrived, add backtotop button to DOM and add scroll and click handlers. - $.when(stringsPromise).then(function(string) { - // Add a fontawesome icon after the footer as the back to top button. - $('#page-footer').after(''); +/** + * Scroll event handler. + * @param {element} button The back-to-top button. + * @param {integer} scrollDistance Scroll distance from the top when to show the back-to-top button. + */ +const handleScroll = (button, scrollDistance) => { + button.style.display = document.documentElement.scrollTop > scrollDistance ? 'block' : 'none'; +}; - // Check directly if the button should be shown. - // This is helpful for all cases when this code here runs _after_ the page has been scrolled, - // especially by the scrollspy feature or by a simple browser page reload. - if ($(window).scrollTop() > scrolldistance) { - checkAndShow(); - } else { - checkAndHide(); - } +/** + * Scroll to top behavior. + * + * @param {event} event + */ +const scrollToTop = (event) => { + event.preventDefault(); + window.scrollTo({ + top: 0, + left: 0, + behavior: 'smooth' + }); +}; - // This function fades the button in when the page is scrolled down or fades it out - // if the user is at the top of the page again. - $(window).on('scroll', function() { - if ($(window).scrollTop() > scrolldistance) { - checkAndShow(); - } else { - checkAndHide(); - } - }); +/** + * Throttle function to limit calls. + * + * @param {function} func The function to throttle. + * @param {number} limit The time interval in milliseconds to throttle the function. + * + * @returns {function} The throttled function. + */ +const throttle = (func, limit) => { + let inThrottle = false; - // This function scrolls the page to top with a duration of 500ms. - $('#back-to-top').on('click', function(event) { - event.preventDefault(); - $('html, body').animate({scrollTop: 0}, 500); - $('#back-to-top').blur(); - }); + return (...args) => { + if (!inThrottle) { + func.apply(this, args); + inThrottle = true; + setTimeout(() => { + inThrottle = false; + }, limit); + } + }; +}; - // This will check if there is a communication button shown on the page already. - // If yes, it will add a class to the body tag which will be later used to align the back-to-top button - // with the communications button. - // This is necessary as the communications button would otherwise be overlaid by the back-to-top button. - if ($('#page-footer .btn-footer-communication').length) { - $('body').addClass('theme-boost-union-commincourse'); - } +/** + * Initial setup for the back to top button. + */ +export const init = async() => { + // Configuration value when to start showing the back-to-top button. + const scrollDistance = 220; - return true; - }).fail(Notification.exception); - } + // Aria text used for the back-to-top button. + const backToTopString = await getString('backtotop', 'theme_boost_union'); - /** - * Helper function to handle the button visibility when the page is scrolling up. - */ - function checkAndHide() { - // Check if the button is still shown. - if (buttonShown === true) { - // Fade it out and remember the status in the end. - // To be precise, the faceOut() function will be called multiple times as buttonShown is not set until the button is - // really faded out. However, as soon as it is faded out, it won't be called until the button is shown again. - $('#back-to-top').fadeOut(100, function() { - buttonShown = false; - }); - } - } + // Create and add the back-to-top button to the DOM. + const footer = document.querySelector('#page-footer'); + const button = createBackToTopButton(backToTopString); + footer.after(button); - /** - * Helper function to handle the button visibility when the page is scrolling down. - */ - function checkAndShow() { - // Check if the button is not yet shown. - if (buttonShown === false) { - // Fade it in and remember the status in the end. - $('#back-to-top').fadeIn(300, function() { - buttonShown = true; - }); - } - } + // Add event listeners that toggle the visibility and the behavior of the back-to-top button. + const throttledScrollHandler = throttle(() => handleScroll(button, scrollDistance), 200); - return { - init: function() { - initBackToTop(); - } - }; -}); + document.addEventListener('scroll', throttledScrollHandler); + button.addEventListener('click', scrollToTop); +};