MOBILE-3814 collapsible: Wait to dom and loading with component registry
parent
81e227a8fa
commit
f38372642d
|
@ -19,8 +19,8 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
import { CoreMath } from '@singletons/math';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreFormatTextDirective } from './format-text';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreEventObserver } from '@singletons/events';
|
||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected async waitFormatTextsRendered(element: Element): Promise<void> {
|
||||
const formatTexts = Array
|
||||
.from(element.querySelectorAll('core-format-text'))
|
||||
.map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective));
|
||||
|
||||
await Promise.all(formatTexts.map(formatText => formatText?.rendered()));
|
||||
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreFormatTextDirective>(
|
||||
element,
|
||||
'core-format-text',
|
||||
'rendered',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -184,24 +184,33 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
|||
* @inheritdoc
|
||||
*/
|
||||
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.
|
||||
this.appearOnBottom = !CoreUtils.isFalseOrZero(this.appearOnBottom);
|
||||
|
||||
// Recalculate the height if a parent core-loading displays the content.
|
||||
this.loadingChangedListener =
|
||||
CoreEvents.on(CoreEvents.CORE_LOADING_CHANGED, async (data: CoreEventLoadingChangedData) => {
|
||||
if (data.loaded && CoreDomUtils.closest(this.element.parentElement, '#' + data.uniqueId)) {
|
||||
// The format-text is inside the loading, re-calculate the height.
|
||||
await this.waitLoadingsDone();
|
||||
|
||||
await this.calculateHeight();
|
||||
setTimeout(() => this.calculateHeight(), 200);
|
||||
|
||||
this.listenScrollEvents();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 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',
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -284,16 +284,25 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
|||
* @return Promise resolved when loadings are done.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param element Element.
|
||||
* @return Promise resolved when texts are rendered.
|
||||
*/
|
||||
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',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Directive, ElementRef, Input, OnInit } from '@angular/core';
|
||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CoreEventLoadingChangedData, CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreFormatTextDirective } from './format-text';
|
||||
|
||||
const defaultMaxHeight = 80;
|
||||
|
@ -47,7 +47,6 @@ export class CoreCollapsibleItemDirective implements OnInit {
|
|||
protected expanded = false;
|
||||
protected maxHeight = defaultMaxHeight;
|
||||
protected expandedHeight = 0;
|
||||
protected loadingChangedListener?: CoreEventObserver;
|
||||
|
||||
constructor(el: ElementRef<HTMLElement>) {
|
||||
this.element = el.nativeElement;
|
||||
|
@ -79,17 +78,22 @@ export class CoreCollapsibleItemDirective implements OnInit {
|
|||
|
||||
this.element.classList.add('collapsible-item');
|
||||
|
||||
// Calculate the height now.
|
||||
await this.calculateHeight();
|
||||
await this.waitLoadingsDone();
|
||||
|
||||
// Recalculate the height if a parent core-loading displays the content.
|
||||
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.
|
||||
await this.calculateHeight();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Wait until all <core-loading> children inside the page.
|
||||
*
|
||||
* @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));
|
||||
|
||||
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.
|
||||
*/
|
||||
protected async calculateHeight(retries = 3): Promise<void> {
|
||||
protected async calculateHeight(): Promise<void> {
|
||||
// Remove max-height (if any) to calculate the real height.
|
||||
this.element.classList.add('collapsible-loading-height');
|
||||
|
||||
await this.waitFormatTextsRendered(this.element);
|
||||
|
||||
await CoreUtils.nextTick();
|
||||
|
||||
this.expandedHeight = CoreDomUtils.getElementHeight(this.element) || 0;
|
||||
|
||||
// Restore the max height now.
|
||||
|
@ -129,10 +133,6 @@ export class CoreCollapsibleItemDirective implements OnInit {
|
|||
|
||||
// If cannot calculate height, shorten always.
|
||||
this.setExpandButtonEnabled(!this.expandedHeight || this.expandedHeight >= this.maxHeight);
|
||||
|
||||
if (this.expandedHeight == 0 && retries > 0) {
|
||||
setTimeout(() => this.calculateHeight(retries - 1), 200);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -53,6 +53,7 @@ import { NavigationStart } from '@angular/router';
|
|||
import { filter } from 'rxjs/operators';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
|
||||
/*
|
||||
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
||||
|
@ -91,6 +92,45 @@ export class CoreDomUtilsProvider {
|
|||
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
|
||||
* traverse the parents to achieve the same functionality.
|
||||
|
|
|
@ -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 selector Selector to search on parent.
|
||||
* @param fnName Function that needs to get resolved.
|
||||
* @param params Params of function that needs to get resolved.
|
||||
* @param fnName Component function that have to be resolved when rendered.
|
||||
* @param params Params of function that have to be resolved when rendered.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
static async resolveAllElementsInside<T = Component>(
|
||||
static async finishRenderingAllElementsInside<T = Component>(
|
||||
element: Element | undefined | null,
|
||||
selector: string,
|
||||
fnName: string,
|
||||
|
@ -77,6 +77,8 @@ export class CoreComponentsRegistry {
|
|||
|
||||
return component[fnName].apply(component, params);
|
||||
}));
|
||||
|
||||
// Wait for next tick to ensure components are completely rendered.
|
||||
await CoreUtils.nextTick();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue