From abb970f87cb1e918e08fea18dbbd444970fa3f1a Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 26 Jul 2024 12:33:30 +0800 Subject: [PATCH] MDL-82373 core_message: Make message drawer router behat-aware The drawer router was modifying the DOM in a variety of ways and hiding content, without any pendingJS checks. As a result, there were cases where a part of the UI was not visible, but had been rendered and was in the DOM. This change wraps the router Go method in a pendingJS call that only resolves when the router transition completes. Additionally the lazy load manager had a similar issue. --- .../amd/build/message_drawer_lazy_load_list.min.js | 2 +- .../build/message_drawer_lazy_load_list.min.js.map | 2 +- message/amd/build/message_drawer_router.min.js | 2 +- message/amd/build/message_drawer_router.min.js.map | 2 +- message/amd/src/message_drawer_lazy_load_list.js | 11 +++++++++-- message/amd/src/message_drawer_router.js | 6 +++++- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/message/amd/build/message_drawer_lazy_load_list.min.js b/message/amd/build/message_drawer_lazy_load_list.min.js index 8e0373e0f77..d5e2eaad4b4 100644 --- a/message/amd/build/message_drawer_lazy_load_list.min.js +++ b/message/amd/build/message_drawer_lazy_load_list.min.js @@ -5,6 +5,6 @@ * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("core_message/message_drawer_lazy_load_list",["jquery","core/custom_interaction_events"],(function($,CustomEvents){var SELECTORS_ROOT='[data-region="lazy-load-list"]',SELECTORS_LOADING_ICON_CONTAINER='[data-region="loading-icon-container"]',SELECTORS_CONTENT_CONTAINER='[data-region="content-container"]',SELECTORS_EMPTY_MESSAGE='[data-region="empty-message-container"]',SELECTORS_PLACEHOLDER='[data-region="placeholder-container"]',stopLoading=function(root){root.attr("data-loading",!1)},getContentContainer=function(root){return root.find(SELECTORS_CONTENT_CONTAINER)},hideLoadingIcon=function(root){root.find(SELECTORS_LOADING_ICON_CONTAINER).addClass("hidden")},showEmptyMessage=function(root){root.find(SELECTORS_EMPTY_MESSAGE).removeClass("hidden")},hidePlaceholder=function(root){root.find(SELECTORS_PLACEHOLDER).addClass("hidden")},showContent=function(root){getContentContainer(root).removeClass("hidden")},hideContent=function(root){getContentContainer(root).addClass("hidden")},setLoadedAll=function(root,value){root.attr("data-loaded-all",value)},loadAndRender=function(root,loadCallback,renderCallback){var userId=function(root){return root.attr("data-user-id")}(root);return function(root){root.attr("data-loading",!0)}(root),loadCallback(root,userId).then((function(items){if(items.length>0){var contentContainer=getContentContainer(root);return renderCallback(contentContainer,items,userId).then((function(){return items}))}return items})).then((function(items){return stopLoading(root),root.attr("data-seen",!0),items.length||setLoadedAll(root,!0),items})).catch((function(){stopLoading(root),root.attr("data-seen",!0)}))},initialLoadAndRender=function(root,loadCallback,renderCallback){return getContentContainer(root).empty(),function(root){root.find(SELECTORS_PLACEHOLDER).removeClass("hidden")}(root),hideContent(root),loadAndRender(root,loadCallback,renderCallback).then((function(items){hidePlaceholder(root),items.length?showContent(root):showEmptyMessage(root)})).catch((function(){hidePlaceholder(root),showContent(root)}))},registerEventListeners=function(root,loadCallback,renderCallback){CustomEvents.define(root,[CustomEvents.events.scrollBottom]),root.on(CustomEvents.events.scrollBottom,(function(){(function(root){return!function(root){return"true"==root.attr("data-loaded-all")}(root)&&!function(root){return"true"===root.attr("data-loading")}(root)})(root)&&(!function(root){root.find(SELECTORS_LOADING_ICON_CONTAINER).removeClass("hidden")}(root),loadAndRender(root,loadCallback,renderCallback).then((function(){return hideLoadingIcon(root)})).catch((function(){return hideLoadingIcon(root)})))}))};return{show:function(root,loadCallback,renderCallback){(root=$(root)).attr("data-init")||(registerEventListeners(root,loadCallback,renderCallback),initialLoadAndRender(root,loadCallback,renderCallback),root.attr("data-init",!0))},getContentContainer:getContentContainer,getRoot:function(containerElement){return containerElement.find(SELECTORS_ROOT)},setLoadedAll:setLoadedAll,showEmptyMessage:showEmptyMessage,hideEmptyMessage:function(root){root.find(SELECTORS_EMPTY_MESSAGE).addClass("hidden")},showContent:showContent,hideContent:hideContent}})); +define("core_message/message_drawer_lazy_load_list",["jquery","core/custom_interaction_events","core/pending"],(function($,CustomEvents,PendingPromise){var SELECTORS_ROOT='[data-region="lazy-load-list"]',SELECTORS_LOADING_ICON_CONTAINER='[data-region="loading-icon-container"]',SELECTORS_CONTENT_CONTAINER='[data-region="content-container"]',SELECTORS_EMPTY_MESSAGE='[data-region="empty-message-container"]',SELECTORS_PLACEHOLDER='[data-region="placeholder-container"]',stopLoading=function(root){root.attr("data-loading",!1)},getContentContainer=function(root){return root.find(SELECTORS_CONTENT_CONTAINER)},hideLoadingIcon=function(root){root.find(SELECTORS_LOADING_ICON_CONTAINER).addClass("hidden")},showEmptyMessage=function(root){root.find(SELECTORS_EMPTY_MESSAGE).removeClass("hidden")},hidePlaceholder=function(root){root.find(SELECTORS_PLACEHOLDER).addClass("hidden")},showContent=function(root){getContentContainer(root).removeClass("hidden")},hideContent=function(root){getContentContainer(root).addClass("hidden")},setLoadedAll=function(root,value){root.attr("data-loaded-all",value)},loadAndRender=function(root,loadCallback,renderCallback){var userId=function(root){return root.attr("data-user-id")}(root);return function(root){root.attr("data-loading",!0)}(root),loadCallback(root,userId).then((function(items){if(items.length>0){var contentContainer=getContentContainer(root);return renderCallback(contentContainer,items,userId).then((function(){return items}))}return items})).then((function(items){return stopLoading(root),root.attr("data-seen",!0),items.length||setLoadedAll(root,!0),items})).catch((function(){stopLoading(root),root.attr("data-seen",!0)}))},initialLoadAndRender=function(root,loadCallback,renderCallback){const pendingPromise=new PendingPromise("initialLoadAndRender");return getContentContainer(root).empty(),function(root){root.find(SELECTORS_PLACEHOLDER).removeClass("hidden")}(root),hideContent(root),loadAndRender(root,loadCallback,renderCallback).then((function(items){hidePlaceholder(root),items.length?showContent(root):showEmptyMessage(root)})).catch((function(){hidePlaceholder(root),showContent(root)})).then((()=>{pendingPromise.resolve()}))},registerEventListeners=function(root,loadCallback,renderCallback){CustomEvents.define(root,[CustomEvents.events.scrollBottom]),root.on(CustomEvents.events.scrollBottom,(function(){(function(root){return!function(root){return"true"==root.attr("data-loaded-all")}(root)&&!function(root){return"true"===root.attr("data-loading")}(root)})(root)&&(!function(root){root.find(SELECTORS_LOADING_ICON_CONTAINER).removeClass("hidden")}(root),loadAndRender(root,loadCallback,renderCallback).then((function(){return hideLoadingIcon(root)})).catch((function(){return hideLoadingIcon(root)})))}))};return{show:function(root,loadCallback,renderCallback){(root=$(root)).attr("data-init")||(registerEventListeners(root,loadCallback,renderCallback),initialLoadAndRender(root,loadCallback,renderCallback),root.attr("data-init",!0))},getContentContainer:getContentContainer,getRoot:function(containerElement){return containerElement.find(SELECTORS_ROOT)},setLoadedAll:setLoadedAll,showEmptyMessage:showEmptyMessage,hideEmptyMessage:function(root){root.find(SELECTORS_EMPTY_MESSAGE).addClass("hidden")},showContent:showContent,hideContent:hideContent}})); //# sourceMappingURL=message_drawer_lazy_load_list.min.js.map \ No newline at end of file diff --git a/message/amd/build/message_drawer_lazy_load_list.min.js.map b/message/amd/build/message_drawer_lazy_load_list.min.js.map index 9914b66d1f6..bd0a14ad256 100644 --- a/message/amd/build/message_drawer_lazy_load_list.min.js.map +++ b/message/amd/build/message_drawer_lazy_load_list.min.js.map @@ -1 +1 @@ -{"version":3,"file":"message_drawer_lazy_load_list.min.js","sources":["../src/message_drawer_lazy_load_list.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 * Lazy loaded list of items.\n *\n * @module core_message/message_drawer_lazy_load_list\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events'\n],\nfunction(\n $,\n CustomEvents\n) {\n\n var SELECTORS = {\n ROOT: '[data-region=\"lazy-load-list\"]',\n LOADING_ICON_CONTAINER: '[data-region=\"loading-icon-container\"]',\n CONTENT_CONTAINER: '[data-region=\"content-container\"]',\n EMPTY_MESSAGE: '[data-region=\"empty-message-container\"]',\n PLACEHOLDER: '[data-region=\"placeholder-container\"]'\n };\n\n /**\n * Flag element as loading.\n *\n * @param {Object} root The section container element.\n */\n var startLoading = function(root) {\n root.attr('data-loading', true);\n };\n\n /**\n * Flag element as not loading.\n *\n * @param {Object} root The section container element.\n */\n var stopLoading = function(root) {\n root.attr('data-loading', false);\n };\n\n /**\n * Check if the element is loading.\n *\n * @param {Object} root The section container element.\n * @return {Bool}\n */\n var isLoading = function(root) {\n return root.attr('data-loading') === 'true';\n };\n\n /**\n * Get user id\n *\n * @param {Object} root The section container element.\n * @return {Number} Logged in user id.\n */\n var getUserId = function(root) {\n return root.attr('data-user-id');\n };\n\n /**\n * Get the section content container element.\n *\n * @param {Object} root The section container element.\n * @return {Object} The section content container element.\n */\n var getContentContainer = function(root) {\n return root.find(SELECTORS.CONTENT_CONTAINER);\n };\n\n /**\n * Get the root element.\n *\n * @param {Object} containerElement The container element to search in.\n * @return {Object} The list root element.\n */\n var getRoot = function(containerElement) {\n return containerElement.find(SELECTORS.ROOT);\n };\n\n /**\n * Show the loading icon.\n *\n * @param {Object} root The section container element.\n */\n var showLoadingIcon = function(root) {\n root.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the loading icon.\n *\n * @param {Object} root The section container element.\n */\n var hideLoadingIcon = function(root) {\n root.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the empty message.\n *\n * @param {Object} root The section container element.\n */\n var showEmptyMessage = function(root) {\n root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');\n };\n\n /**\n * Hide the empty message.\n *\n * @param {Object} root The section container element.\n */\n var hideEmptyMessage = function(root) {\n root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');\n };\n\n /**\n * Show the placeholder element.\n *\n * @param {Object} root The section container element.\n */\n var showPlaceholder = function(root) {\n root.find(SELECTORS.PLACEHOLDER).removeClass('hidden');\n };\n\n /**\n * Hide the placeholder element.\n *\n * @param {Object} root The section container element.\n */\n var hidePlaceholder = function(root) {\n root.find(SELECTORS.PLACEHOLDER).addClass('hidden');\n };\n\n /**\n * Show the section content container.\n *\n * @param {Object} root The section container element.\n */\n var showContent = function(root) {\n getContentContainer(root).removeClass('hidden');\n };\n\n /**\n * Hide the section content container.\n *\n * @param {Object} root The section container element.\n */\n var hideContent = function(root) {\n getContentContainer(root).addClass('hidden');\n };\n\n /**\n * If the section has loaded all content.\n *\n * @param {Object} root The section container element.\n * @return {Bool}\n */\n var hasLoadedAll = function(root) {\n return root.attr('data-loaded-all') == 'true';\n };\n\n /**\n * If the section has loaded all content.\n *\n * @param {Object} root The section container element.\n * @param {Bool} value If all items have been loaded.\n */\n var setLoadedAll = function(root, value) {\n root.attr('data-loaded-all', value);\n };\n\n /**\n * If the section can load more items.\n *\n * @param {Object} root The section container element.\n * @return {Bool}\n */\n var canLoadMore = function(root) {\n return !hasLoadedAll(root) && !isLoading(root);\n };\n\n /**\n * Load all items in this container from callback and render them.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n * @return {Object} jQuery promise\n */\n var loadAndRender = function(root, loadCallback, renderCallback) {\n var userId = getUserId(root);\n startLoading(root);\n\n return loadCallback(root, userId)\n .then(function(items) {\n if (items.length > 0) {\n var contentContainer = getContentContainer(root);\n return renderCallback(contentContainer, items, userId)\n .then(function() {\n return items;\n });\n } else {\n return items;\n }\n })\n .then(function(items) {\n stopLoading(root);\n root.attr('data-seen', true);\n\n if (!items.length) {\n setLoadedAll(root, true);\n }\n\n return items;\n })\n .catch(function() {\n stopLoading(root);\n root.attr('data-seen', true);\n return;\n });\n };\n\n /**\n * First load of this section.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n * @return {Object} promise\n */\n var initialLoadAndRender = function(root, loadCallback, renderCallback) {\n getContentContainer(root).empty();\n showPlaceholder(root);\n hideContent(root);\n return loadAndRender(root, loadCallback, renderCallback)\n .then(function(items) {\n hidePlaceholder(root);\n\n if (!items.length) {\n showEmptyMessage(root);\n } else {\n showContent(root);\n }\n\n return;\n })\n .catch(function() {\n hidePlaceholder(root);\n showContent(root);\n return;\n });\n };\n\n /**\n * Listen to, and handle events in this section.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n */\n var registerEventListeners = function(root, loadCallback, renderCallback) {\n CustomEvents.define(root, [\n CustomEvents.events.scrollBottom\n ]);\n\n root.on(CustomEvents.events.scrollBottom, function() {\n if (canLoadMore(root)) {\n showLoadingIcon(root);\n loadAndRender(root, loadCallback, renderCallback)\n .then(function() {\n return hideLoadingIcon(root);\n })\n .catch(function() {\n return hideLoadingIcon(root);\n });\n }\n });\n };\n\n /**\n * Setup the section.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n */\n var show = function(root, loadCallback, renderCallback) {\n root = $(root);\n\n if (!root.attr('data-init')) {\n registerEventListeners(root, loadCallback, renderCallback);\n initialLoadAndRender(root, loadCallback, renderCallback);\n root.attr('data-init', true);\n }\n };\n\n return {\n show: show,\n getContentContainer: getContentContainer,\n getRoot: getRoot,\n setLoadedAll: setLoadedAll,\n showEmptyMessage: showEmptyMessage,\n hideEmptyMessage: hideEmptyMessage,\n showContent: showContent,\n hideContent: hideContent\n };\n});\n"],"names":["define","$","CustomEvents","SELECTORS","stopLoading","root","attr","getContentContainer","find","hideLoadingIcon","addClass","showEmptyMessage","removeClass","hidePlaceholder","showContent","hideContent","setLoadedAll","value","loadAndRender","loadCallback","renderCallback","userId","getUserId","startLoading","then","items","length","contentContainer","catch","initialLoadAndRender","empty","showPlaceholder","registerEventListeners","events","scrollBottom","on","hasLoadedAll","isLoading","canLoadMore","showLoadingIcon","show","getRoot","containerElement","hideEmptyMessage"],"mappings":";;;;;;;AAsBAA,oDACA,CACI,SACA,mCAEJ,SACIC,EACAC,kBAGIC,eACM,iCADNA,iCAEwB,yCAFxBA,4BAGmB,oCAHnBA,wBAIe,0CAJfA,sBAKa,wCAiBbC,YAAc,SAASC,MACvBA,KAAKC,KAAK,gBAAgB,IA6B1BC,oBAAsB,SAASF,aACxBA,KAAKG,KAAKL,8BA2BjBM,gBAAkB,SAASJ,MAC3BA,KAAKG,KAAKL,kCAAkCO,SAAS,WAQrDC,iBAAmB,SAASN,MAC5BA,KAAKG,KAAKL,yBAAyBS,YAAY,WA0B/CC,gBAAkB,SAASR,MAC3BA,KAAKG,KAAKL,uBAAuBO,SAAS,WAQ1CI,YAAc,SAAST,MACvBE,oBAAoBF,MAAMO,YAAY,WAQtCG,YAAc,SAASV,MACvBE,oBAAoBF,MAAMK,SAAS,WAmBnCM,aAAe,SAASX,KAAMY,OAC9BZ,KAAKC,KAAK,kBAAmBW,QAqB7BC,cAAgB,SAASb,KAAMc,aAAcC,oBACzCC,OAvIQ,SAAShB,aACdA,KAAKC,KAAK,gBAsIJgB,CAAUjB,aApKR,SAASA,MACxBA,KAAKC,KAAK,gBAAgB,GAoK1BiB,CAAalB,MAENc,aAAad,KAAMgB,QACrBG,MAAK,SAASC,UACPA,MAAMC,OAAS,EAAG,KACdC,iBAAmBpB,oBAAoBF,aACpCe,eAAeO,iBAAkBF,MAAOJ,QAC1CG,MAAK,kBACKC,gBAGRA,SAGdD,MAAK,SAASC,cACXrB,YAAYC,MACZA,KAAKC,KAAK,aAAa,GAElBmB,MAAMC,QACPV,aAAaX,MAAM,GAGhBoB,SAEVG,OAAM,WACHxB,YAAYC,MACZA,KAAKC,KAAK,aAAa,OAa/BuB,qBAAuB,SAASxB,KAAMc,aAAcC,uBACpDb,oBAAoBF,MAAMyB,QA/GR,SAASzB,MAC3BA,KAAKG,KAAKL,uBAAuBS,YAAY,UA+G7CmB,CAAgB1B,MAChBU,YAAYV,MACLa,cAAcb,KAAMc,aAAcC,gBACpCI,MAAK,SAASC,OACXZ,gBAAgBR,MAEXoB,MAAMC,OAGPZ,YAAYT,MAFZM,iBAAiBN,SAOxBuB,OAAM,WACHf,gBAAgBR,MAChBS,YAAYT,UAYpB2B,uBAAyB,SAAS3B,KAAMc,aAAcC,gBACtDlB,aAAaF,OAAOK,KAAM,CACtBH,aAAa+B,OAAOC,eAGxB7B,KAAK8B,GAAGjC,aAAa+B,OAAOC,cAAc,YAxF5B,SAAS7B,aApBR,SAASA,YACe,QAAhCA,KAAKC,KAAK,mBAoBT8B,CAAa/B,QArIT,SAASA,YACgB,SAA9BA,KAAKC,KAAK,gBAoIc+B,CAAUhC,OAwFjCiC,CAAYjC,SAtLF,SAASA,MAC3BA,KAAKG,KAAKL,kCAAkCS,YAAY,UAsLhD2B,CAAgBlC,MAChBa,cAAcb,KAAMc,aAAcC,gBAC7BI,MAAK,kBACKf,gBAAgBJ,SAE1BuB,OAAM,kBACInB,gBAAgBJ,oBAuBpC,CACHmC,KAXO,SAASnC,KAAMc,aAAcC,iBACpCf,KAAOJ,EAAEI,OAECC,KAAK,eACX0B,uBAAuB3B,KAAMc,aAAcC,gBAC3CS,qBAAqBxB,KAAMc,aAAcC,gBACzCf,KAAKC,KAAK,aAAa,KAM3BC,oBAAqBA,oBACrBkC,QAhOU,SAASC,yBACZA,iBAAiBlC,KAAKL,iBAgO7Ba,aAAcA,aACdL,iBAAkBA,iBAClBgC,iBA/LmB,SAAStC,MAC5BA,KAAKG,KAAKL,yBAAyBO,SAAS,WA+L5CI,YAAaA,YACbC,YAAaA"} \ No newline at end of file +{"version":3,"file":"message_drawer_lazy_load_list.min.js","sources":["../src/message_drawer_lazy_load_list.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 * Lazy loaded list of items.\n *\n * @module core_message/message_drawer_lazy_load_list\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/pending',\n],\nfunction(\n $,\n CustomEvents,\n PendingPromise,\n) {\n\n var SELECTORS = {\n ROOT: '[data-region=\"lazy-load-list\"]',\n LOADING_ICON_CONTAINER: '[data-region=\"loading-icon-container\"]',\n CONTENT_CONTAINER: '[data-region=\"content-container\"]',\n EMPTY_MESSAGE: '[data-region=\"empty-message-container\"]',\n PLACEHOLDER: '[data-region=\"placeholder-container\"]'\n };\n\n /**\n * Flag element as loading.\n *\n * @param {Object} root The section container element.\n */\n var startLoading = function(root) {\n root.attr('data-loading', true);\n };\n\n /**\n * Flag element as not loading.\n *\n * @param {Object} root The section container element.\n */\n var stopLoading = function(root) {\n root.attr('data-loading', false);\n };\n\n /**\n * Check if the element is loading.\n *\n * @param {Object} root The section container element.\n * @return {Bool}\n */\n var isLoading = function(root) {\n return root.attr('data-loading') === 'true';\n };\n\n /**\n * Get user id\n *\n * @param {Object} root The section container element.\n * @return {Number} Logged in user id.\n */\n var getUserId = function(root) {\n return root.attr('data-user-id');\n };\n\n /**\n * Get the section content container element.\n *\n * @param {Object} root The section container element.\n * @return {Object} The section content container element.\n */\n var getContentContainer = function(root) {\n return root.find(SELECTORS.CONTENT_CONTAINER);\n };\n\n /**\n * Get the root element.\n *\n * @param {Object} containerElement The container element to search in.\n * @return {Object} The list root element.\n */\n var getRoot = function(containerElement) {\n return containerElement.find(SELECTORS.ROOT);\n };\n\n /**\n * Show the loading icon.\n *\n * @param {Object} root The section container element.\n */\n var showLoadingIcon = function(root) {\n root.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the loading icon.\n *\n * @param {Object} root The section container element.\n */\n var hideLoadingIcon = function(root) {\n root.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the empty message.\n *\n * @param {Object} root The section container element.\n */\n var showEmptyMessage = function(root) {\n root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');\n };\n\n /**\n * Hide the empty message.\n *\n * @param {Object} root The section container element.\n */\n var hideEmptyMessage = function(root) {\n root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');\n };\n\n /**\n * Show the placeholder element.\n *\n * @param {Object} root The section container element.\n */\n var showPlaceholder = function(root) {\n root.find(SELECTORS.PLACEHOLDER).removeClass('hidden');\n };\n\n /**\n * Hide the placeholder element.\n *\n * @param {Object} root The section container element.\n */\n var hidePlaceholder = function(root) {\n root.find(SELECTORS.PLACEHOLDER).addClass('hidden');\n };\n\n /**\n * Show the section content container.\n *\n * @param {Object} root The section container element.\n */\n var showContent = function(root) {\n getContentContainer(root).removeClass('hidden');\n };\n\n /**\n * Hide the section content container.\n *\n * @param {Object} root The section container element.\n */\n var hideContent = function(root) {\n getContentContainer(root).addClass('hidden');\n };\n\n /**\n * If the section has loaded all content.\n *\n * @param {Object} root The section container element.\n * @return {Bool}\n */\n var hasLoadedAll = function(root) {\n return root.attr('data-loaded-all') == 'true';\n };\n\n /**\n * If the section has loaded all content.\n *\n * @param {Object} root The section container element.\n * @param {Bool} value If all items have been loaded.\n */\n var setLoadedAll = function(root, value) {\n root.attr('data-loaded-all', value);\n };\n\n /**\n * If the section can load more items.\n *\n * @param {Object} root The section container element.\n * @return {Bool}\n */\n var canLoadMore = function(root) {\n return !hasLoadedAll(root) && !isLoading(root);\n };\n\n /**\n * Load all items in this container from callback and render them.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n * @return {Object} jQuery promise\n */\n var loadAndRender = function(root, loadCallback, renderCallback) {\n var userId = getUserId(root);\n startLoading(root);\n\n return loadCallback(root, userId)\n .then(function(items) {\n if (items.length > 0) {\n var contentContainer = getContentContainer(root);\n return renderCallback(contentContainer, items, userId)\n .then(function() {\n return items;\n });\n } else {\n return items;\n }\n })\n .then(function(items) {\n stopLoading(root);\n root.attr('data-seen', true);\n\n if (!items.length) {\n setLoadedAll(root, true);\n }\n\n return items;\n })\n .catch(function() {\n stopLoading(root);\n root.attr('data-seen', true);\n return;\n });\n };\n\n /**\n * First load of this section.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n * @return {Object} promise\n */\n var initialLoadAndRender = function(root, loadCallback, renderCallback) {\n const pendingPromise = new PendingPromise('initialLoadAndRender');\n getContentContainer(root).empty();\n showPlaceholder(root);\n hideContent(root);\n return loadAndRender(root, loadCallback, renderCallback)\n .then(function(items) {\n hidePlaceholder(root);\n\n if (!items.length) {\n showEmptyMessage(root);\n } else {\n showContent(root);\n }\n\n return;\n })\n .catch(function() {\n hidePlaceholder(root);\n showContent(root);\n return;\n })\n .then(() => {\n pendingPromise.resolve();\n return;\n });\n };\n\n /**\n * Listen to, and handle events in this section.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n */\n var registerEventListeners = function(root, loadCallback, renderCallback) {\n CustomEvents.define(root, [\n CustomEvents.events.scrollBottom\n ]);\n\n root.on(CustomEvents.events.scrollBottom, function() {\n if (canLoadMore(root)) {\n showLoadingIcon(root);\n loadAndRender(root, loadCallback, renderCallback)\n .then(function() {\n return hideLoadingIcon(root);\n })\n .catch(function() {\n return hideLoadingIcon(root);\n });\n }\n });\n };\n\n /**\n * Setup the section.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n */\n var show = function(root, loadCallback, renderCallback) {\n root = $(root);\n\n if (!root.attr('data-init')) {\n registerEventListeners(root, loadCallback, renderCallback);\n initialLoadAndRender(root, loadCallback, renderCallback);\n root.attr('data-init', true);\n }\n };\n\n return {\n show: show,\n getContentContainer: getContentContainer,\n getRoot: getRoot,\n setLoadedAll: setLoadedAll,\n showEmptyMessage: showEmptyMessage,\n hideEmptyMessage: hideEmptyMessage,\n showContent: showContent,\n hideContent: hideContent\n };\n});\n"],"names":["define","$","CustomEvents","PendingPromise","SELECTORS","stopLoading","root","attr","getContentContainer","find","hideLoadingIcon","addClass","showEmptyMessage","removeClass","hidePlaceholder","showContent","hideContent","setLoadedAll","value","loadAndRender","loadCallback","renderCallback","userId","getUserId","startLoading","then","items","length","contentContainer","catch","initialLoadAndRender","pendingPromise","empty","showPlaceholder","resolve","registerEventListeners","events","scrollBottom","on","hasLoadedAll","isLoading","canLoadMore","showLoadingIcon","show","getRoot","containerElement","hideEmptyMessage"],"mappings":";;;;;;;AAsBAA,oDACA,CACI,SACA,iCACA,iBAEJ,SACIC,EACAC,aACAC,oBAGIC,eACM,iCADNA,iCAEwB,yCAFxBA,4BAGmB,oCAHnBA,wBAIe,0CAJfA,sBAKa,wCAiBbC,YAAc,SAASC,MACvBA,KAAKC,KAAK,gBAAgB,IA6B1BC,oBAAsB,SAASF,aACxBA,KAAKG,KAAKL,8BA2BjBM,gBAAkB,SAASJ,MAC3BA,KAAKG,KAAKL,kCAAkCO,SAAS,WAQrDC,iBAAmB,SAASN,MAC5BA,KAAKG,KAAKL,yBAAyBS,YAAY,WA0B/CC,gBAAkB,SAASR,MAC3BA,KAAKG,KAAKL,uBAAuBO,SAAS,WAQ1CI,YAAc,SAAST,MACvBE,oBAAoBF,MAAMO,YAAY,WAQtCG,YAAc,SAASV,MACvBE,oBAAoBF,MAAMK,SAAS,WAmBnCM,aAAe,SAASX,KAAMY,OAC9BZ,KAAKC,KAAK,kBAAmBW,QAqB7BC,cAAgB,SAASb,KAAMc,aAAcC,oBACzCC,OAvIQ,SAAShB,aACdA,KAAKC,KAAK,gBAsIJgB,CAAUjB,aApKR,SAASA,MACxBA,KAAKC,KAAK,gBAAgB,GAoK1BiB,CAAalB,MAENc,aAAad,KAAMgB,QACrBG,MAAK,SAASC,UACPA,MAAMC,OAAS,EAAG,KACdC,iBAAmBpB,oBAAoBF,aACpCe,eAAeO,iBAAkBF,MAAOJ,QAC1CG,MAAK,kBACKC,gBAGRA,SAGdD,MAAK,SAASC,cACXrB,YAAYC,MACZA,KAAKC,KAAK,aAAa,GAElBmB,MAAMC,QACPV,aAAaX,MAAM,GAGhBoB,SAEVG,OAAM,WACHxB,YAAYC,MACZA,KAAKC,KAAK,aAAa,OAa/BuB,qBAAuB,SAASxB,KAAMc,aAAcC,sBAC9CU,eAAiB,IAAI5B,eAAe,+BAC1CK,oBAAoBF,MAAM0B,QAhHR,SAAS1B,MAC3BA,KAAKG,KAAKL,uBAAuBS,YAAY,UAgH7CoB,CAAgB3B,MAChBU,YAAYV,MACLa,cAAcb,KAAMc,aAAcC,gBACpCI,MAAK,SAASC,OACXZ,gBAAgBR,MAEXoB,MAAMC,OAGPZ,YAAYT,MAFZM,iBAAiBN,SAOxBuB,OAAM,WACHf,gBAAgBR,MAChBS,YAAYT,SAGfmB,MAAK,KACFM,eAAeG,cAYvBC,uBAAyB,SAAS7B,KAAMc,aAAcC,gBACtDnB,aAAaF,OAAOM,KAAM,CACtBJ,aAAakC,OAAOC,eAGxB/B,KAAKgC,GAAGpC,aAAakC,OAAOC,cAAc,YA7F5B,SAAS/B,aApBR,SAASA,YACe,QAAhCA,KAAKC,KAAK,mBAoBTgC,CAAajC,QArIT,SAASA,YACgB,SAA9BA,KAAKC,KAAK,gBAoIciC,CAAUlC,OA6FjCmC,CAAYnC,SA3LF,SAASA,MAC3BA,KAAKG,KAAKL,kCAAkCS,YAAY,UA2LhD6B,CAAgBpC,MAChBa,cAAcb,KAAMc,aAAcC,gBAC7BI,MAAK,kBACKf,gBAAgBJ,SAE1BuB,OAAM,kBACInB,gBAAgBJ,oBAuBpC,CACHqC,KAXO,SAASrC,KAAMc,aAAcC,iBACpCf,KAAOL,EAAEK,OAECC,KAAK,eACX4B,uBAAuB7B,KAAMc,aAAcC,gBAC3CS,qBAAqBxB,KAAMc,aAAcC,gBACzCf,KAAKC,KAAK,aAAa,KAM3BC,oBAAqBA,oBACrBoC,QArOU,SAASC,yBACZA,iBAAiBpC,KAAKL,iBAqO7Ba,aAAcA,aACdL,iBAAkBA,iBAClBkC,iBApMmB,SAASxC,MAC5BA,KAAKG,KAAKL,yBAAyBO,SAAS,WAoM5CI,YAAaA,YACbC,YAAaA"} \ No newline at end of file diff --git a/message/amd/build/message_drawer_router.min.js b/message/amd/build/message_drawer_router.min.js index 0884644ba6f..6acc445abf7 100644 --- a/message/amd/build/message_drawer_router.min.js +++ b/message/amd/build/message_drawer_router.min.js @@ -9,6 +9,6 @@ * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("core_message/message_drawer_router",["jquery","core/pubsub","core/str","core_message/message_drawer_events","core/aria"],(function($,PubSub,Str,MessageDrawerEvents,Aria){var routes={},history={},SELECTORS_CAN_RECEIVE_FOCUS='input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',SELECTORS_ROUTES_BACK="[data-route-back]",changeRoute=function(namespace,newRoute){var newConfig,fromPanel=[].slice.call(arguments).some((function(arg){return"frompanel"==arg})),args=[].slice.call(arguments,2),renderPromise=$.Deferred().resolve().promise();if(Object.keys(routes[namespace]).forEach((function(route){var config=routes[namespace][route],isMatch=route===newRoute;isMatch&&(newConfig=config),config.parameters.forEach((function(element){"object"==typeof element&&null!==element&&(element.removeClass("previous"),element.attr("data-from-panel",!1),isMatch?(fromPanel&&element.attr("data-from-panel",!0),element.removeClass("hidden"),Aria.unhide(element.get())):element.attr("data-in-panel")&&"view-search"!=newRoute&&"view-overview"!=newRoute||(element.addClass("hidden"),Aria.hide(element.get())))}))})),newConfig&&newConfig.onGo){renderPromise=newConfig.onGo.apply(void 0,newConfig.parameters.concat(args));for(var currentFocusElement=$(document.activeElement),hasFocus=!1,firstFocusable=null,i=1;ipendingPromise.resolve())),record},go=function(namespace){var currentFocusElement=$(document.activeElement),record=changeRoute.apply(namespace,arguments),inHistory=!1;history[namespace]||(history[namespace]=[]),history[namespace]=history[namespace].reduce((function(carry,previous){return previous.route===record.route&&(inHistory=!0),inHistory||carry.push(previous),carry}),[]);var historylength=history[namespace].length,previousRecord=historylength?history[namespace][historylength-1]:null;if(previousRecord){for(var prevConfig=routes[namespace][previousRecord.route],elements=prevConfig.parameters,i=1;i.\n\n/**\n * A simple router for the message drawer that allows navigating between\n * the \"pages\" in the drawer.\n *\n * This module will maintain a linear history of the unique pages access\n * to allow navigating back.\n *\n * @module core_message/message_drawer_router\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/pubsub',\n 'core/str',\n 'core_message/message_drawer_events',\n 'core/aria',\n],\nfunction(\n $,\n PubSub,\n Str,\n MessageDrawerEvents,\n Aria\n) {\n\n /* @var {object} routes Message drawer route elements and callbacks. */\n var routes = {};\n\n /* @var {object} history Store for route objects history. */\n var history = {};\n\n var SELECTORS = {\n CAN_RECEIVE_FOCUS: 'input:not([type=\"hidden\"]), a[href], button, textarea, select, [tabindex]',\n ROUTES_BACK: '[data-route-back]'\n };\n\n /**\n * Add a route.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {string} route Route config name.\n * @param {array} parameters Route parameters.\n * @param {callback} onGo Route initialization function.\n * @param {callback} getDescription Route initialization function.\n */\n var add = function(namespace, route, parameters, onGo, getDescription) {\n if (!routes[namespace]) {\n routes[namespace] = [];\n }\n\n routes[namespace][route] =\n {\n parameters: parameters,\n onGo: onGo,\n getDescription: getDescription\n };\n };\n\n /**\n * Go to a defined route and run the route callbacks.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {string} newRoute Route config name.\n * @return {object} record Current route record with route config name and parameters.\n */\n var changeRoute = function(namespace, newRoute) {\n var newConfig;\n\n // Check if the Route change call is made from an element in the app panel.\n var fromPanel = [].slice.call(arguments).some(function(arg) {\n return arg == 'frompanel';\n });\n // Get the rest of the arguments, if any.\n var args = [].slice.call(arguments, 2);\n var renderPromise = $.Deferred().resolve().promise();\n\n Object.keys(routes[namespace]).forEach(function(route) {\n var config = routes[namespace][route];\n var isMatch = route === newRoute;\n\n if (isMatch) {\n newConfig = config;\n }\n\n config.parameters.forEach(function(element) {\n // Some parameters may be null, or not an element.\n if (typeof element !== 'object' || element === null) {\n return;\n }\n\n element.removeClass('previous');\n element.attr('data-from-panel', false);\n\n if (isMatch) {\n if (fromPanel) {\n // Set this attribute to let the conversation renderer know not to show a back button.\n element.attr('data-from-panel', true);\n }\n element.removeClass('hidden');\n Aria.unhide(element.get());\n } else {\n // For the message index page elements in the left panel should not be hidden.\n if (!element.attr('data-in-panel')) {\n element.addClass('hidden');\n Aria.hide(element.get());\n } else if (newRoute == 'view-search' || newRoute == 'view-overview') {\n element.addClass('hidden');\n Aria.hide(element.get());\n }\n }\n });\n });\n\n if (newConfig) {\n if (newConfig.onGo) {\n renderPromise = newConfig.onGo.apply(undefined, newConfig.parameters.concat(args));\n var currentFocusElement = $(document.activeElement);\n var hasFocus = false;\n var firstFocusable = null;\n\n // No need to start at 0 as we know that is the namespace.\n for (var i = 1; i < newConfig.parameters.length; i++) {\n var element = newConfig.parameters[i];\n\n // Some parameters may be null, or not an element.\n if (typeof element !== 'object' || element === null) {\n continue;\n }\n\n if (!firstFocusable) {\n firstFocusable = element;\n }\n\n if (element.has(currentFocusElement).length) {\n hasFocus = true;\n break;\n }\n }\n\n if (!hasFocus) {\n // This page doesn't have focus yet so focus the first focusable\n // element in the new view.\n firstFocusable.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(':visible').first().focus();\n }\n }\n }\n\n var record = {\n route: newRoute,\n params: args,\n renderPromise: renderPromise\n };\n\n PubSub.publish(MessageDrawerEvents.ROUTE_CHANGED, record);\n\n return record;\n };\n\n /**\n * Go to a defined route and store the route history.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @return {object} record Current route record with route config name and parameters.\n */\n var go = function(namespace) {\n var currentFocusElement = $(document.activeElement);\n\n var record = changeRoute.apply(namespace, arguments);\n var inHistory = false;\n\n if (!history[namespace]) {\n history[namespace] = [];\n }\n\n // History stores a unique list of routes. Check to see if the new route\n // is already in the history, if it is then forget all history after it.\n // This ensures there are no duplicate routes in history and that it represents\n // a linear path of routes (it never stores something like [foo, bar, foo])).\n history[namespace] = history[namespace].reduce(function(carry, previous) {\n if (previous.route === record.route) {\n inHistory = true;\n }\n\n if (!inHistory) {\n carry.push(previous);\n }\n\n return carry;\n }, []);\n\n var historylength = history[namespace].length;\n var previousRecord = historylength ? history[namespace][historylength - 1] : null;\n\n if (previousRecord) {\n var prevConfig = routes[namespace][previousRecord.route];\n var elements = prevConfig.parameters;\n\n // The first one will be the namespace, skip it.\n for (var i = 1; i < elements.length; i++) {\n // Some parameters may be null, or not an element.\n if (typeof elements[i] !== 'object' || elements[i] === null) {\n continue;\n }\n\n elements[i].addClass('previous');\n }\n\n previousRecord.focusElement = currentFocusElement;\n\n if (prevConfig.getDescription) {\n // If the route has a description then set it on the back button for\n // the new page we're displaying.\n prevConfig.getDescription.apply(null, prevConfig.parameters.concat(previousRecord.params))\n .then(function(description) {\n return Str.get_string('backto', 'core_message', description);\n })\n .then(function(label) {\n // Wait for the new page to finish rendering so that we know\n // that the back button is visible.\n return record.renderPromise.then(function() {\n // Find the elements for the new route we displayed.\n routes[namespace][record.route].parameters.forEach(function(element) {\n // Some parameters may be null, or not an element.\n if (typeof element !== 'object' || !element) {\n return;\n }\n // Update the aria label for the back button.\n element.find(SELECTORS.ROUTES_BACK).attr('aria-label', label);\n });\n });\n })\n .catch(function() {\n // Silently ignore.\n });\n }\n }\n history[namespace].push(record);\n return record;\n };\n\n /**\n * Go back to the previous route record stored in history.\n *\n * @param {String} namespace Unique identifier for the Routes\n */\n var back = function(namespace) {\n if (history[namespace].length) {\n // Remove the current route.\n history[namespace].pop();\n var previous = history[namespace].pop();\n\n if (previous) {\n // If we have a previous route then show it.\n go.apply(undefined, [namespace, previous.route].concat(previous.params));\n // Delay the focus 50 milliseconds otherwise it doesn't correctly\n // focus the element for some reason...\n window.setTimeout(function() {\n previous.focusElement.focus();\n }, 50);\n }\n }\n };\n\n return {\n add: add,\n go: go,\n back: back\n };\n});\n"],"names":["define","$","PubSub","Str","MessageDrawerEvents","Aria","routes","history","SELECTORS","changeRoute","namespace","newRoute","newConfig","fromPanel","slice","call","arguments","some","arg","args","renderPromise","Deferred","resolve","promise","Object","keys","forEach","route","config","isMatch","parameters","element","removeClass","attr","unhide","get","addClass","hide","onGo","apply","undefined","concat","currentFocusElement","document","activeElement","hasFocus","firstFocusable","i","length","has","find","filter","first","focus","record","params","publish","ROUTE_CHANGED","go","inHistory","reduce","carry","previous","push","historylength","previousRecord","prevConfig","elements","focusElement","getDescription","then","description","get_string","label","catch","add","back","pop","window","setTimeout"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"message_drawer_router.min.js","sources":["../src/message_drawer_router.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 * A simple router for the message drawer that allows navigating between\n * the \"pages\" in the drawer.\n *\n * This module will maintain a linear history of the unique pages access\n * to allow navigating back.\n *\n * @module core_message/message_drawer_router\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/pubsub',\n 'core/str',\n 'core_message/message_drawer_events',\n 'core/aria',\n 'core/pending',\n],\nfunction(\n $,\n PubSub,\n Str,\n MessageDrawerEvents,\n Aria,\n PendingPromise,\n) {\n\n /* @var {object} routes Message drawer route elements and callbacks. */\n var routes = {};\n\n /* @var {object} history Store for route objects history. */\n var history = {};\n\n var SELECTORS = {\n CAN_RECEIVE_FOCUS: 'input:not([type=\"hidden\"]), a[href], button, textarea, select, [tabindex]',\n ROUTES_BACK: '[data-route-back]'\n };\n\n /**\n * Add a route.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {string} route Route config name.\n * @param {array} parameters Route parameters.\n * @param {callback} onGo Route initialization function.\n * @param {callback} getDescription Route initialization function.\n */\n var add = function(namespace, route, parameters, onGo, getDescription) {\n if (!routes[namespace]) {\n routes[namespace] = [];\n }\n\n routes[namespace][route] =\n {\n parameters: parameters,\n onGo: onGo,\n getDescription: getDescription\n };\n };\n\n /**\n * Go to a defined route and run the route callbacks.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {string} newRoute Route config name.\n * @return {object} record Current route record with route config name and parameters.\n */\n var changeRoute = function(namespace, newRoute) {\n var newConfig;\n var pendingPromise = new PendingPromise(`message-drawer-router-${namespace}-${newRoute}`);\n\n // Check if the Route change call is made from an element in the app panel.\n var fromPanel = [].slice.call(arguments).some(function(arg) {\n return arg == 'frompanel';\n });\n // Get the rest of the arguments, if any.\n var args = [].slice.call(arguments, 2);\n var renderPromise = $.Deferred().resolve().promise();\n\n Object.keys(routes[namespace]).forEach(function(route) {\n var config = routes[namespace][route];\n var isMatch = route === newRoute;\n\n if (isMatch) {\n newConfig = config;\n }\n\n config.parameters.forEach(function(element) {\n // Some parameters may be null, or not an element.\n if (typeof element !== 'object' || element === null) {\n return;\n }\n\n element.removeClass('previous');\n element.attr('data-from-panel', false);\n\n if (isMatch) {\n if (fromPanel) {\n // Set this attribute to let the conversation renderer know not to show a back button.\n element.attr('data-from-panel', true);\n }\n element.removeClass('hidden');\n Aria.unhide(element.get());\n } else {\n // For the message index page elements in the left panel should not be hidden.\n if (!element.attr('data-in-panel')) {\n element.addClass('hidden');\n Aria.hide(element.get());\n } else if (newRoute == 'view-search' || newRoute == 'view-overview') {\n element.addClass('hidden');\n Aria.hide(element.get());\n }\n }\n });\n });\n\n if (newConfig) {\n if (newConfig.onGo) {\n renderPromise = newConfig.onGo.apply(undefined, newConfig.parameters.concat(args));\n var currentFocusElement = $(document.activeElement);\n var hasFocus = false;\n var firstFocusable = null;\n\n // No need to start at 0 as we know that is the namespace.\n for (var i = 1; i < newConfig.parameters.length; i++) {\n var element = newConfig.parameters[i];\n\n // Some parameters may be null, or not an element.\n if (typeof element !== 'object' || element === null) {\n continue;\n }\n\n if (!firstFocusable) {\n firstFocusable = element;\n }\n\n if (element.has(currentFocusElement).length) {\n hasFocus = true;\n break;\n }\n }\n\n if (!hasFocus) {\n // This page doesn't have focus yet so focus the first focusable\n // element in the new view.\n firstFocusable.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(':visible').first().focus();\n }\n }\n }\n\n var record = {\n route: newRoute,\n params: args,\n renderPromise: renderPromise\n };\n\n PubSub.publish(MessageDrawerEvents.ROUTE_CHANGED, record);\n\n renderPromise.then(() => pendingPromise.resolve());\n return record;\n };\n\n /**\n * Go to a defined route and store the route history.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @return {object} record Current route record with route config name and parameters.\n */\n var go = function(namespace) {\n var currentFocusElement = $(document.activeElement);\n\n var record = changeRoute.apply(namespace, arguments);\n var inHistory = false;\n\n if (!history[namespace]) {\n history[namespace] = [];\n }\n\n // History stores a unique list of routes. Check to see if the new route\n // is already in the history, if it is then forget all history after it.\n // This ensures there are no duplicate routes in history and that it represents\n // a linear path of routes (it never stores something like [foo, bar, foo])).\n history[namespace] = history[namespace].reduce(function(carry, previous) {\n if (previous.route === record.route) {\n inHistory = true;\n }\n\n if (!inHistory) {\n carry.push(previous);\n }\n\n return carry;\n }, []);\n\n var historylength = history[namespace].length;\n var previousRecord = historylength ? history[namespace][historylength - 1] : null;\n\n if (previousRecord) {\n var prevConfig = routes[namespace][previousRecord.route];\n var elements = prevConfig.parameters;\n\n // The first one will be the namespace, skip it.\n for (var i = 1; i < elements.length; i++) {\n // Some parameters may be null, or not an element.\n if (typeof elements[i] !== 'object' || elements[i] === null) {\n continue;\n }\n\n elements[i].addClass('previous');\n }\n\n previousRecord.focusElement = currentFocusElement;\n\n if (prevConfig.getDescription) {\n // If the route has a description then set it on the back button for\n // the new page we're displaying.\n prevConfig.getDescription.apply(null, prevConfig.parameters.concat(previousRecord.params))\n .then(function(description) {\n return Str.get_string('backto', 'core_message', description);\n })\n .then(function(label) {\n // Wait for the new page to finish rendering so that we know\n // that the back button is visible.\n return record.renderPromise.then(function() {\n // Find the elements for the new route we displayed.\n routes[namespace][record.route].parameters.forEach(function(element) {\n // Some parameters may be null, or not an element.\n if (typeof element !== 'object' || !element) {\n return;\n }\n // Update the aria label for the back button.\n element.find(SELECTORS.ROUTES_BACK).attr('aria-label', label);\n });\n });\n })\n .catch(function() {\n // Silently ignore.\n });\n }\n }\n history[namespace].push(record);\n return record;\n };\n\n /**\n * Go back to the previous route record stored in history.\n *\n * @param {String} namespace Unique identifier for the Routes\n */\n var back = function(namespace) {\n if (history[namespace].length) {\n // Remove the current route.\n history[namespace].pop();\n var previous = history[namespace].pop();\n\n if (previous) {\n // If we have a previous route then show it.\n go.apply(undefined, [namespace, previous.route].concat(previous.params));\n // Delay the focus 50 milliseconds otherwise it doesn't correctly\n // focus the element for some reason...\n window.setTimeout(function() {\n previous.focusElement.focus();\n }, 50);\n }\n }\n };\n\n return {\n add: add,\n go: go,\n back: back\n };\n});\n"],"names":["define","$","PubSub","Str","MessageDrawerEvents","Aria","PendingPromise","routes","history","SELECTORS","changeRoute","namespace","newRoute","newConfig","pendingPromise","fromPanel","slice","call","arguments","some","arg","args","renderPromise","Deferred","resolve","promise","Object","keys","forEach","route","config","isMatch","parameters","element","removeClass","attr","unhide","get","addClass","hide","onGo","apply","undefined","concat","currentFocusElement","document","activeElement","hasFocus","firstFocusable","i","length","has","find","filter","first","focus","record","params","publish","ROUTE_CHANGED","then","go","inHistory","reduce","carry","previous","push","historylength","previousRecord","prevConfig","elements","focusElement","getDescription","description","get_string","label","catch","add","back","pop","window","setTimeout"],"mappings":"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"} \ No newline at end of file diff --git a/message/amd/src/message_drawer_lazy_load_list.js b/message/amd/src/message_drawer_lazy_load_list.js index b4003182983..6d7bd916908 100644 --- a/message/amd/src/message_drawer_lazy_load_list.js +++ b/message/amd/src/message_drawer_lazy_load_list.js @@ -23,11 +23,13 @@ define( [ 'jquery', - 'core/custom_interaction_events' + 'core/custom_interaction_events', + 'core/pending', ], function( $, - CustomEvents + CustomEvents, + PendingPromise, ) { var SELECTORS = { @@ -248,6 +250,7 @@ function( * @return {Object} promise */ var initialLoadAndRender = function(root, loadCallback, renderCallback) { + const pendingPromise = new PendingPromise('initialLoadAndRender'); getContentContainer(root).empty(); showPlaceholder(root); hideContent(root); @@ -267,6 +270,10 @@ function( hidePlaceholder(root); showContent(root); return; + }) + .then(() => { + pendingPromise.resolve(); + return; }); }; diff --git a/message/amd/src/message_drawer_router.js b/message/amd/src/message_drawer_router.js index 119e08189b2..23517762bb9 100644 --- a/message/amd/src/message_drawer_router.js +++ b/message/amd/src/message_drawer_router.js @@ -31,13 +31,15 @@ define( 'core/str', 'core_message/message_drawer_events', 'core/aria', + 'core/pending', ], function( $, PubSub, Str, MessageDrawerEvents, - Aria + Aria, + PendingPromise, ) { /* @var {object} routes Message drawer route elements and callbacks. */ @@ -82,6 +84,7 @@ function( */ var changeRoute = function(namespace, newRoute) { var newConfig; + var pendingPromise = new PendingPromise(`message-drawer-router-${namespace}-${newRoute}`); // Check if the Route change call is made from an element in the app panel. var fromPanel = [].slice.call(arguments).some(function(arg) { @@ -170,6 +173,7 @@ function( PubSub.publish(MessageDrawerEvents.ROUTE_CHANGED, record); + renderPromise.then(() => pendingPromise.resolve()); return record; };