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
// 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 = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.dragHomes()));
async cloneDragItems(): Promise<void> {
const dragHomes = Array.from(this.container.querySelectorAll<HTMLElement>(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 = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drags()));
const drags = Array.from(this.container.querySelectorAll<HTMLElement>(this.selectors.drags()));
drags.forEach((drag) => {
drag.classList.remove('selected');
});
@ -192,19 +194,13 @@ export class AddonQtypeDdwtosQuestion {
* Initialize the question.
*/
async initializer(): Promise<void> {
this.selectors = new AddonQtypeDdwtosQuestionCSSSelectors();
const container = <HTMLElement> this.container.querySelector(this.selectors.topNode());
if (this.readOnly) {
container.classList.add('readonly');
} else {
container.classList.add('notreadonly');
}
const container = this.container.querySelector<HTMLElement>(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 = <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.
drags.forEach((drag) => {
@ -292,15 +288,15 @@ export class AddonQtypeDdwtosQuestion {
}
// 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) => {
this.makeDropZone(drop);
});
// 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;
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 = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.drags()));
const drags = Array.from(this.container.querySelectorAll<HTMLElement>(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<void> {
const drag = <HTMLElement | null> 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<void> {
await CoreDomUtils.waitToBeInDOM(this.container);
const deferred = CoreUtils.promiseDefer<void>();
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<HTMLElement>(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 = <HTMLElement> this.container.querySelector(this.selectors.dropForPlace(placeNo));
const drop = this.container.querySelector<HTMLElement>(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<void> {
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 = <HTMLElement[]> Array.from(this.container.querySelectorAll(this.selectors.dragHomesGroup(groupNo)));
async setPaddingSizeForGroup(groupNo: number): Promise<void> {
const groupItems = Array.from(this.container.querySelectorAll<HTMLElement>(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 = <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) => {
this.padToWidthHeight(item, maxWidth + 2, maxHeight + 2);
});