MOBILE-3814 ddwtos: Use dom Promises to know if element is ready

main
Pau Ferrer Ocaña 2022-03-21 11:29:46 +01:00
parent 505891fa11
commit dbc91004e4
1 changed files with 34 additions and 43 deletions

View File

@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { CoreFormatTextDirective } from '@directives/format-text';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreEventObserver } from '@singletons/events'; import { CoreEventObserver } from '@singletons/events';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { AddonModQuizDdwtosQuestionData } from '../component/ddwtos'; import { AddonModQuizDdwtosQuestionData } from '../component/ddwtos';
@ -26,7 +28,7 @@ export class AddonQtypeDdwtosQuestion {
protected logger: CoreLogger; protected logger: CoreLogger;
protected nextDragItemNo = 1; 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 placed: {[no: number]: number} = {}; // Map that relates drag elements numbers with drop zones numbers.
protected selected?: HTMLElement; // Selected element (being "dragged"). protected selected?: HTMLElement; // Selected element (being "dragged").
protected resizeListener?: CoreEventObserver; 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. * 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. * We clone these invisible elements to make the actual drag items.
*/ */
cloneDragItems(): void { async cloneDragItems(): Promise<void> {
const dragHomes = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.dragHomes())); const dragHomes = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.dragHomes()));
for (let x = 0; x < dragHomes.length; x++) { for (let x = 0; x < dragHomes.length; x++) {
this.cloneDragItemsForOneChoice(dragHomes[x]); this.cloneDragItemsForOneChoice(dragHomes[x]);
} }
@ -110,7 +112,7 @@ export class AddonQtypeDdwtosQuestion {
*/ */
deselectDrags(): void { deselectDrags(): void {
// Remove the selected class from all drags. // Remove the selected class from all drags.
const drags = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drags())); const drags = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.drags()));
drags.forEach((drag) => { drags.forEach((drag) => {
drag.classList.remove('selected'); drag.classList.remove('selected');
}); });
@ -192,19 +194,13 @@ export class AddonQtypeDdwtosQuestion {
* Initialize the question. * Initialize the question.
*/ */
async initializer(): Promise<void> { async initializer(): Promise<void> {
this.selectors = new AddonQtypeDdwtosQuestionCSSSelectors(); const container = this.container.querySelector<HTMLElement>(this.selectors.topNode());
container?.classList.add(this.readOnly ? 'readonly' : 'notreadonly');
const container = <HTMLElement> this.container.querySelector(this.selectors.topNode());
if (this.readOnly) {
container.classList.add('readonly');
} else {
container.classList.add('notreadonly');
}
// Wait for the elements to be ready. // Wait for the elements to be ready.
await this.waitForReady(); await this.waitForReady();
this.setPaddingSizesAll(); await this.setPaddingSizesAll();
this.cloneDragItems(); this.cloneDragItems();
this.initialPlaceOfDragItems(); this.initialPlaceOfDragItems();
this.makeDropZones(); this.makeDropZones();
@ -220,7 +216,7 @@ export class AddonQtypeDdwtosQuestion {
* Initialize drag items, putting them in their initial place. * Initialize drag items, putting them in their initial place.
*/ */
initialPlaceOfDragItems(): void { initialPlaceOfDragItems(): void {
const drags = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drags())); const drags = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.drags()));
// Add the class 'unplaced' to all elements. // Add the class 'unplaced' to all elements.
drags.forEach((drag) => { drags.forEach((drag) => {
@ -292,15 +288,15 @@ export class AddonQtypeDdwtosQuestion {
} }
// Create all the drop zones. // Create all the drop zones.
const drops = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drops())); const drops = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.drops()));
drops.forEach((drop) => { drops.forEach((drop) => {
this.makeDropZone(drop); this.makeDropZone(drop);
}); });
// If home answer zone is clicked, return drag home. // If home answer zone is clicked, return drag home.
const home = <HTMLElement> this.container.querySelector(this.selectors.topNode() + ' .answercontainer'); const home = this.container.querySelector<HTMLElement>(this.selectors.topNode() + ' .answercontainer');
home.addEventListener('click', () => { home?.addEventListener('click', () => {
const drag = this.selected; const drag = this.selected;
if (!drag) { if (!drag) {
// No element selected, nothing to do. // 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. * Postition, or reposition, all the drag items. They're placed in the right drop zone or in the home zone.
*/ */
positionDragItems(): void { positionDragItems(): void {
const drags = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drags())); const drags = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.drags()));
drags.forEach((drag) => { drags.forEach((drag) => {
this.positionDragItem(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 in the DOM.
* @return Promise resolved when ready or if it took too long to load.
*/ */
protected async waitForReady(retries: number = 0): Promise<void> { protected async waitForReady(): Promise<void> {
const drag = <HTMLElement | null> Array.from(this.container.querySelectorAll(this.selectors.drags()))[0]; await CoreDomUtils.waitToBeInDOM(this.container);
if (drag?.offsetParent || retries >= 10) {
// Ready or too many retries, stop.
return;
}
const deferred = CoreUtils.promiseDefer<void>(); await CoreComponentsRegistry.waitComponentsReady(this.container, 'core-format-text', CoreFormatTextDirective);
setTimeout(async () => { const drag = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.dragHomes()))[0];
try {
await this.waitForReady(retries + 1);
} finally {
deferred.resolve();
}
}, 20);
return deferred.promise; await CoreDomUtils.waitToBeInDOM(drag);
} }
/** /**
@ -452,7 +437,7 @@ export class AddonQtypeDdwtosQuestion {
*/ */
removeDragFromDrop(drag: HTMLElement): void { removeDragFromDrop(drag: HTMLElement): void {
const placeNo = this.placed[this.getNo(drag) ?? -1]; const placeNo = this.placed[this.getNo(drag) ?? -1];
const drop = <HTMLElement> this.container.querySelector(this.selectors.dropForPlace(placeNo)); const drop = this.container.querySelector<HTMLElement>(this.selectors.dropForPlace(placeNo));
this.placeDragInDrop(null, drop); this.placeDragInDrop(null, drop);
} }
@ -473,9 +458,9 @@ export class AddonQtypeDdwtosQuestion {
/** /**
* Set the padding size for all groups. * Set the padding size for all groups.
*/ */
setPaddingSizesAll(): void { async setPaddingSizesAll(): Promise<void> {
for (let groupNo = 1; groupNo <= 8; groupNo++) { 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. * @param groupNo Group number.
*/ */
setPaddingSizeForGroup(groupNo: number): void { async setPaddingSizeForGroup(groupNo: number): Promise<void> {
const groupItems = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.dragHomesGroup(groupNo))); const groupItems = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.dragHomesGroup(groupNo)));
if (!groupItems.length) { if (!groupItems.length) {
return; return;
} }
await CoreDomUtils.waitToBeInDOM(groupItems[0]);
let maxWidth = 0; let maxWidth = 0;
let maxHeight = 0; let maxHeight = 0;
// Find max height and width. // Find max height and width.
groupItems.forEach((item) => { groupItems.forEach((item) => {
item.innerHTML = CoreTextUtils.decodeHTML(item.innerHTML); 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)); maxWidth = Math.max(maxWidth, Math.ceil(item.offsetWidth));
maxHeight = Math.max(maxHeight, Math.ceil(item.offsetHeight)); maxHeight = Math.max(maxHeight, Math.ceil(item.offsetHeight));
}); });
@ -507,7 +498,7 @@ export class AddonQtypeDdwtosQuestion {
this.padToWidthHeight(item, maxWidth, maxHeight); this.padToWidthHeight(item, maxWidth, maxHeight);
}); });
const dropsGroup = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.dropsGroup(groupNo))); const dropsGroup = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.dropsGroup(groupNo)));
dropsGroup.forEach((item) => { dropsGroup.forEach((item) => {
this.padToWidthHeight(item, maxWidth + 2, maxHeight + 2); this.padToWidthHeight(item, maxWidth + 2, maxHeight + 2);
}); });