From dbc91004e4391f26519f82f844f5ec987390f78e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 21 Mar 2022 11:29:46 +0100 Subject: [PATCH] MOBILE-3814 ddwtos: Use dom Promises to know if element is ready --- src/addons/qtype/ddwtos/classes/ddwtos.ts | 77 ++++++++++------------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/src/addons/qtype/ddwtos/classes/ddwtos.ts b/src/addons/qtype/ddwtos/classes/ddwtos.ts index 63c4fcc6c..29ab292dc 100644 --- a/src/addons/qtype/ddwtos/classes/ddwtos.ts +++ b/src/addons/qtype/ddwtos/classes/ddwtos.ts @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreFormatTextDirective } from '@directives/format-text'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; +import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreEventObserver } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; import { AddonModQuizDdwtosQuestionData } from '../component/ddwtos'; @@ -26,7 +28,7 @@ export class AddonQtypeDdwtosQuestion { protected logger: CoreLogger; protected nextDragItemNo = 1; - protected selectors!: AddonQtypeDdwtosQuestionCSSSelectors; // Result of cssSelectors. + protected selectors = new AddonQtypeDdwtosQuestionCSSSelectors(); // Result of cssSelectors. protected placed: {[no: number]: number} = {}; // Map that relates drag elements numbers with drop zones numbers. protected selected?: HTMLElement; // Selected element (being "dragged"). protected resizeListener?: CoreEventObserver; @@ -80,8 +82,8 @@ export class AddonQtypeDdwtosQuestion { * Invisible 'drag homes' are output in the question. These have the same properties as the drag items but are invisible. * We clone these invisible elements to make the actual drag items. */ - cloneDragItems(): void { - const dragHomes = Array.from(this.container.querySelectorAll(this.selectors.dragHomes())); + async cloneDragItems(): Promise { + const dragHomes = Array.from(this.container.querySelectorAll(this.selectors.dragHomes())); for (let x = 0; x < dragHomes.length; x++) { this.cloneDragItemsForOneChoice(dragHomes[x]); } @@ -110,7 +112,7 @@ export class AddonQtypeDdwtosQuestion { */ deselectDrags(): void { // Remove the selected class from all drags. - const drags = Array.from(this.container.querySelectorAll(this.selectors.drags())); + const drags = Array.from(this.container.querySelectorAll(this.selectors.drags())); drags.forEach((drag) => { drag.classList.remove('selected'); }); @@ -192,19 +194,13 @@ export class AddonQtypeDdwtosQuestion { * Initialize the question. */ async initializer(): Promise { - this.selectors = new AddonQtypeDdwtosQuestionCSSSelectors(); - - const container = this.container.querySelector(this.selectors.topNode()); - if (this.readOnly) { - container.classList.add('readonly'); - } else { - container.classList.add('notreadonly'); - } + const container = this.container.querySelector(this.selectors.topNode()); + container?.classList.add(this.readOnly ? 'readonly' : 'notreadonly'); // Wait for the elements to be ready. await this.waitForReady(); - this.setPaddingSizesAll(); + await this.setPaddingSizesAll(); this.cloneDragItems(); this.initialPlaceOfDragItems(); this.makeDropZones(); @@ -220,7 +216,7 @@ export class AddonQtypeDdwtosQuestion { * Initialize drag items, putting them in their initial place. */ initialPlaceOfDragItems(): void { - const drags = Array.from(this.container.querySelectorAll(this.selectors.drags())); + const drags = Array.from(this.container.querySelectorAll(this.selectors.drags())); // Add the class 'unplaced' to all elements. drags.forEach((drag) => { @@ -292,15 +288,15 @@ export class AddonQtypeDdwtosQuestion { } // Create all the drop zones. - const drops = Array.from(this.container.querySelectorAll(this.selectors.drops())); + const drops = Array.from(this.container.querySelectorAll(this.selectors.drops())); drops.forEach((drop) => { this.makeDropZone(drop); }); // If home answer zone is clicked, return drag home. - const home = this.container.querySelector(this.selectors.topNode() + ' .answercontainer'); + const home = this.container.querySelector(this.selectors.topNode() + ' .answercontainer'); - home.addEventListener('click', () => { + home?.addEventListener('click', () => { const drag = this.selected; if (!drag) { // No element selected, nothing to do. @@ -413,36 +409,25 @@ export class AddonQtypeDdwtosQuestion { * Postition, or reposition, all the drag items. They're placed in the right drop zone or in the home zone. */ positionDragItems(): void { - const drags = Array.from(this.container.querySelectorAll(this.selectors.drags())); + const drags = Array.from(this.container.querySelectorAll(this.selectors.drags())); drags.forEach((drag) => { this.positionDragItem(drag); }); } /** - * Wait for the drag items to have an offsetParent. For some reason it takes a while. + * Wait for the drag home items to be in DOM. * - * @param retries Number of times this has been retried. - * @return Promise resolved when ready or if it took too long to load. + * @return Promise resolved when ready in the DOM. */ - protected async waitForReady(retries: number = 0): Promise { - const drag = Array.from(this.container.querySelectorAll(this.selectors.drags()))[0]; - if (drag?.offsetParent || retries >= 10) { - // Ready or too many retries, stop. - return; - } + protected async waitForReady(): Promise { + await CoreDomUtils.waitToBeInDOM(this.container); - const deferred = CoreUtils.promiseDefer(); + await CoreComponentsRegistry.waitComponentsReady(this.container, 'core-format-text', CoreFormatTextDirective); - setTimeout(async () => { - try { - await this.waitForReady(retries + 1); - } finally { - deferred.resolve(); - } - }, 20); + const drag = Array.from(this.container.querySelectorAll(this.selectors.dragHomes()))[0]; - return deferred.promise; + await CoreDomUtils.waitToBeInDOM(drag); } /** @@ -452,7 +437,7 @@ export class AddonQtypeDdwtosQuestion { */ removeDragFromDrop(drag: HTMLElement): void { const placeNo = this.placed[this.getNo(drag) ?? -1]; - const drop = this.container.querySelector(this.selectors.dropForPlace(placeNo)); + const drop = this.container.querySelector(this.selectors.dropForPlace(placeNo)); this.placeDragInDrop(null, drop); } @@ -473,9 +458,9 @@ export class AddonQtypeDdwtosQuestion { /** * Set the padding size for all groups. */ - setPaddingSizesAll(): void { + async setPaddingSizesAll(): Promise { for (let groupNo = 1; groupNo <= 8; groupNo++) { - this.setPaddingSizeForGroup(groupNo); + await this.setPaddingSizeForGroup(groupNo); } } @@ -484,19 +469,25 @@ export class AddonQtypeDdwtosQuestion { * * @param groupNo Group number. */ - setPaddingSizeForGroup(groupNo: number): void { - const groupItems = Array.from(this.container.querySelectorAll(this.selectors.dragHomesGroup(groupNo))); + async setPaddingSizeForGroup(groupNo: number): Promise { + const groupItems = Array.from(this.container.querySelectorAll(this.selectors.dragHomesGroup(groupNo))); if (!groupItems.length) { return; } + await CoreDomUtils.waitToBeInDOM(groupItems[0]); + let maxWidth = 0; let maxHeight = 0; - // Find max height and width. groupItems.forEach((item) => { item.innerHTML = CoreTextUtils.decodeHTML(item.innerHTML); + }); + // Wait to render in order to calculate size. + await CoreUtils.nextTick(); + + groupItems.forEach((item) => { maxWidth = Math.max(maxWidth, Math.ceil(item.offsetWidth)); maxHeight = Math.max(maxHeight, Math.ceil(item.offsetHeight)); }); @@ -507,7 +498,7 @@ export class AddonQtypeDdwtosQuestion { this.padToWidthHeight(item, maxWidth, maxHeight); }); - const dropsGroup = Array.from(this.container.querySelectorAll(this.selectors.dropsGroup(groupNo))); + const dropsGroup = Array.from(this.container.querySelectorAll(this.selectors.dropsGroup(groupNo))); dropsGroup.forEach((item) => { this.padToWidthHeight(item, maxWidth + 2, maxHeight + 2); });