MOBILE-3814 collapsible: Wait to dom and loading with component registry

main
Pau Ferrer Ocaña 2022-03-11 15:58:02 +01:00
parent 81e227a8fa
commit f38372642d
5 changed files with 107 additions and 47 deletions

View File

@ -19,8 +19,8 @@ import { CoreUtils } from '@services/utils/utils';
import { CoreMath } from '@singletons/math'; import { CoreMath } from '@singletons/math';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreFormatTextDirective } from './format-text'; import { CoreFormatTextDirective } from './format-text';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreEventObserver } from '@singletons/events';
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreLoadingComponent } from '@components/loading/loading';
/** /**
* Directive to make an element fixed at the bottom collapsible when scrolling. * Directive to make an element fixed at the bottom collapsible when scrolling.
@ -139,11 +139,11 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
* @param element Element. * @param element Element.
*/ */
protected async waitFormatTextsRendered(element: Element): Promise<void> { protected async waitFormatTextsRendered(element: Element): Promise<void> {
const formatTexts = Array await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreFormatTextDirective>(
.from(element.querySelectorAll('core-format-text')) element,
.map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective)); 'core-format-text',
'rendered',
await Promise.all(formatTexts.map(formatText => formatText?.rendered())); );
} }
/** /**
@ -184,24 +184,33 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
* @inheritdoc * @inheritdoc
*/ */
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
// Calculate the height now.
await this.calculateHeight();
setTimeout(() => this.calculateHeight(), 200); // Try again, sometimes the first calculation is wrong.
this.listenScrollEvents();
// Only if not present or explicitly falsy it will be false. // Only if not present or explicitly falsy it will be false.
this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom); this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom);
// Recalculate the height if a parent core-loading displays the content. await this.waitLoadingsDone();
this.loadingChangedListener =
CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, async (data: CoreEventLoadingChangedData) => { await this.calculateHeight();
if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) {
// The format-text is inside the loading, re-calculate the height. this.listenScrollEvents();
await this.calculateHeight(); }
setTimeout(() => this.calculateHeight(), 200);
} /**
}); * Wait until all <core-loading> children inside the page.
*
* @return Promise resolved when loadings are done.
*/
protected async waitLoadingsDone(): Promise<void> {
const scrollElement = await this.ionContent.getScrollElement();
await Promise.all([
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>
(scrollElement, 'core-loading', 'whenLoaded'),
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>(
this.element,
'core-loading',
'whenLoaded',
),
]);
} }
/** /**

View File

@ -284,16 +284,25 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
* @return Promise resolved when loadings are done. * @return Promise resolved when loadings are done.
*/ */
protected async waitLoadingsDone(): Promise<void> { protected async waitLoadingsDone(): Promise<void> {
await CoreComponentsRegistry.resolveAllElementsInside<CoreLoadingComponent>(this.page, 'core-loading', 'whenLoaded'); await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>(
this.page,
'core-loading',
'whenLoaded',
);
} }
/** /**
* Wait until all <core-format-text> children inside the element are done rendering. * Wait until all <core-format-text> children inside the element are done rendering.
* *
* @param element Element. * @param element Element.
* @return Promise resolved when texts are rendered.
*/ */
protected async waitFormatTextsRendered(element: Element): Promise<void> { protected async waitFormatTextsRendered(element: Element): Promise<void> {
await CoreComponentsRegistry.resolveAllElementsInside<CoreFormatTextDirective>(element, 'core-format-text', 'rendered'); await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreFormatTextDirective>(
element,
'core-format-text',
'rendered',
);
} }
/** /**

View File

@ -13,11 +13,11 @@
// limitations under the License. // limitations under the License.
import { Directive, ElementRef, Input, OnInit } from '@angular/core'; import { Directive, ElementRef, Input, OnInit } from '@angular/core';
import { CoreLoadingComponent } from '@components/loading/loading';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreFormatTextDirective } from './format-text'; import { CoreFormatTextDirective } from './format-text';
const defaultMaxHeight = 80; const defaultMaxHeight = 80;
@ -47,7 +47,6 @@ export class CoreCollapsibleItemDirective implements OnInit {
protected expanded = false; protected expanded = false;
protected maxHeight = defaultMaxHeight; protected maxHeight = defaultMaxHeight;
protected expandedHeight = 0; protected expandedHeight = 0;
protected loadingChangedListener?: CoreEventObserver;
constructor(el: ElementRef<HTMLElement>) { constructor(el: ElementRef<HTMLElement>) {
this.element = el.nativeElement; this.element = el.nativeElement;
@ -79,17 +78,22 @@ export class CoreCollapsibleItemDirective implements OnInit {
this.element.classList.add('collapsible-item'); this.element.classList.add('collapsible-item');
// Calculate the height now. await this.waitLoadingsDone();
await this.calculateHeight();
// Recalculate the height if a parent core-loading displays the content. await this.calculateHeight();
this.loadingChangedListener = }
CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, async (data: CoreEventLoadingChangedData) => {
if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) { /**
// The element is inside the loading, re-calculate the height. * Wait until all <core-loading> children inside the page.
await this.calculateHeight(); *
} * @return Promise resolved when loadings are done.
}); */
protected async waitLoadingsDone(): Promise<void> {
await CoreDomUtils.waitToDom(this.element);
const page = this.element.closest('.ion-page');
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>(page, 'core-loading', 'whenLoaded');
} }
/** /**
@ -109,19 +113,19 @@ export class CoreCollapsibleItemDirective implements OnInit {
const formatTexts = formatTextElements.map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective)); const formatTexts = formatTextElements.map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective));
await Promise.all(formatTexts.map(formatText => formatText?.rendered())); await Promise.all(formatTexts.map(formatText => formatText?.rendered()));
await CoreUtils.nextTick();
} }
/** /**
* Calculate the height and check if we need to display show more or not. * Calculate the height and check if we need to display show more or not.
*/ */
protected async calculateHeight(retries = 3): Promise<void> { protected async calculateHeight(): Promise<void> {
// Remove max-height (if any) to calculate the real height. // Remove max-height (if any) to calculate the real height.
this.element.classList.add('collapsible-loading-height'); this.element.classList.add('collapsible-loading-height');
await this.waitFormatTextsRendered(this.element); await this.waitFormatTextsRendered(this.element);
await CoreUtils.nextTick();
this.expandedHeight = CoreDomUtils.getElementHeight(this.element) || 0; this.expandedHeight = CoreDomUtils.getElementHeight(this.element) || 0;
// Restore the max height now. // Restore the max height now.
@ -129,10 +133,6 @@ export class CoreCollapsibleItemDirective implements OnInit {
// If cannot calculate height, shorten always. // If cannot calculate height, shorten always.
this.setExpandButtonEnabled(!this.expandedHeight || this.expandedHeight >= this.maxHeight); this.setExpandButtonEnabled(!this.expandedHeight || this.expandedHeight >= this.maxHeight);
if (this.expandedHeight == 0 && retries > 0) {
setTimeout(() => this.calculateHeight(retries - 1), 200);
}
} }
/** /**

View File

@ -53,6 +53,7 @@ import { NavigationStart } from '@angular/router';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreComponentsRegistry } from '@singletons/components-registry';
import { CorePromisedValue } from '@classes/promised-value';
/* /*
* "Utils" service with helper functions for UI, DOM elements and HTML code. * "Utils" service with helper functions for UI, DOM elements and HTML code.
@ -91,6 +92,45 @@ export class CoreDomUtilsProvider {
this.debugDisplay = debugDisplay != 0; this.debugDisplay = debugDisplay != 0;
} }
/**
* Wait an element to be in dom of another element.
*
* @param element Element to wait.
* @return Promise resolved when added. It will be rejected after a timeout of 5s.
*/
waitToDom(
element: Element,
): CorePromisedValue<void> {
let root = element.getRootNode({ composed: true });
const inDomPromise = new CorePromisedValue<void>();
if (root === document) {
// Already in DOM.
inDomPromise.resolve();
return inDomPromise;
}
// Disconnect observer for performance reasons.
const timeout = window.setTimeout(() => {
inDomPromise.reject(new Error('Waiting for DOM timeout reached'));
observer.disconnect();
}, 5000);
const observer = new MutationObserver(() => {
root = element.getRootNode({ composed: true });
if (root === document) {
observer.disconnect();
clearTimeout(timeout);
inDomPromise.resolve();
}
});
observer.observe(document.body, { subtree: true, childList: true });
return inDomPromise;
}
/** /**
* Equivalent to element.closest(). If the browser doesn't support element.closest, it will * Equivalent to element.closest(). If the browser doesn't support element.closest, it will
* traverse the parents to achieve the same functionality. * traverse the parents to achieve the same functionality.

View File

@ -48,15 +48,15 @@ export class CoreComponentsRegistry {
} }
/** /**
* Waits all elements to be resolved. * Waits all elements to be rendered.
* *
* @param element Parent element where to search. * @param element Parent element where to search.
* @param selector Selector to search on parent. * @param selector Selector to search on parent.
* @param fnName Function that needs to get resolved. * @param fnName Component function that have to be resolved when rendered.
* @param params Params of function that needs to get resolved. * @param params Params of function that have to be resolved when rendered.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
static async resolveAllElementsInside<T = Component>( static async finishRenderingAllElementsInside<T = Component>(
element: Element | undefined | null, element: Element | undefined | null,
selector: string, selector: string,
fnName: string, fnName: string,
@ -77,6 +77,8 @@ export class CoreComponentsRegistry {
return component[fnName].apply(component, params); return component[fnName].apply(component, params);
})); }));
// Wait for next tick to ensure components are completely rendered.
await CoreUtils.nextTick(); await CoreUtils.nextTick();
} }