MOBILE-3651 core: Fix scroll to element function

main
Dani Palou 2021-02-19 11:47:22 +01:00
parent 1620dd47ea
commit f682d89e67
6 changed files with 51 additions and 23 deletions

View File

@ -47,6 +47,7 @@ export class AddonModQuizPreflightModalComponent implements OnInit {
constructor(
formBuilder: FormBuilder,
protected elementRef: ElementRef,
) {
// Create an empty form group. The controls will be added by the access rules components.
this.preflightForm = formBuilder.group({});
@ -115,7 +116,12 @@ export class AddonModQuizPreflightModalComponent implements OnInit {
if (!this.preflightForm.valid) {
// Form not valid. Scroll to the first element with errors.
if (!CoreDomUtils.instance.scrollToInputError(this.content)) {
const hasScrolled = CoreDomUtils.instance.scrollToInputError(
this.elementRef.nativeElement,
this.content,
);
if (!hasScrolled) {
// Input not found, show an error modal.
CoreDomUtils.instance.showErrorModal('core.errorinvalidform', true);
}

View File

@ -681,9 +681,11 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
* @param slot Slot of the question to scroll to.
*/
protected scrollToQuestion(slot: number): void {
if (this.content) {
CoreDomUtils.instance.scrollToElementBySelector(this.content, '#addon-mod_quiz-question-' + slot);
}
CoreDomUtils.instance.scrollToElementBySelector(
this.elementRef.nativeElement,
this.content,
'#addon-mod_quiz-question-' + slot,
);
}
/**

View File

@ -104,7 +104,11 @@ export class CoreLinkDirective implements OnInit {
if (href.charAt(0) == '#') {
// Look for id or name.
href = href.substr(1);
CoreDomUtils.instance.scrollToElementBySelector(this.content, '#' + href + ', [name=\'' + href + '\']');
CoreDomUtils.instance.scrollToElementBySelector(
this.element.closest('ion-content'),
this.content,
`#${href}, [name='${href}']`,
);
return;
}

View File

@ -25,6 +25,7 @@ import {
QueryList,
Type,
ViewChild,
ElementRef,
} from '@angular/core';
import { CoreSites } from '@services/sites';
@ -111,6 +112,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
constructor(
protected content: IonContent,
protected elementRef: ElementRef,
) {
// Pass this instance to all components so they can use its methods and properties.
this.data.coreCourseFormatComponent = this;
@ -402,7 +404,11 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
if (this.moduleId && typeof previousValue == 'undefined') {
setTimeout(() => {
CoreDomUtils.instance.scrollToElementBySelector(this.content, '#core-course-module-' + this.moduleId);
CoreDomUtils.instance.scrollToElementBySelector(
this.elementRef.nativeElement,
this.content,
'#core-course-module-' + this.moduleId,
);
}, 200);
} else {
this.content.scrollToTop(0);

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild, ElementRef, OnInit } from '@angular/core';
import { Component, ViewChild, ElementRef, OnInit, ChangeDetectorRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { IonContent, IonRefresher } from '@ionic/angular';
@ -81,6 +81,8 @@ export class CoreLoginEmailSignupPage implements OnInit {
constructor(
protected fb: FormBuilder,
protected elementRef: ElementRef,
protected changeDetector: ChangeDetectorRef,
) {
// Create the ageVerificationForm.
this.ageVerificationForm = this.fb.group({
@ -272,8 +274,17 @@ export class CoreLoginEmailSignupPage implements OnInit {
e.stopPropagation();
if (!this.signupForm.valid || (this.settings?.recaptchapublickey && !this.captcha.recaptcharesponse)) {
// Form not valid. Scroll to the first element with errors.
const errorFound = await CoreDomUtils.instance.scrollToInputError(this.content);
// Form not valid. Mark all controls as dirty to display errors.
for (const name in this.signupForm.controls) {
this.signupForm.controls[name].markAsDirty();
}
this.changeDetector.detectChanges();
// Scroll to the first element with errors.
const errorFound = CoreDomUtils.instance.scrollToInputError(
this.elementRef.nativeElement,
this.content,
);
if (!errorFound) {
// Input not found, show an error modal.

View File

@ -1119,24 +1119,26 @@ export class CoreDomUtilsProvider {
/**
* Scroll to a certain element using a selector to find it.
*
* @param container The element that contains the element that must be scrolled.
* @param content The content that must be scrolled.
* @param selector Selector to find the element to scroll to.
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll.
* @param duration Duration of the scroll animation in milliseconds.
* @return True if the element is found, false otherwise.
*/
async scrollToElementBySelector(
content: IonContent,
scrollToElementBySelector(
container: HTMLElement | null,
content: IonContent | undefined,
selector: string,
scrollParentClass?: string,
duration?: number,
): Promise<boolean> {
// @todo: This function is broken. Scroll element cannot be used because it uses shadow DOM so querySelector returns null.
// Also, traversing using parentElement doesn't work either, offsetParent isn't part of the parentElement tree.
try {
const scrollElement = await content.getScrollElement();
): boolean {
if (!container || !content) {
return false;
}
const position = this.getElementXY(scrollElement, selector, scrollParentClass);
try {
const position = this.getElementXY(container, selector, scrollParentClass);
if (!position) {
return false;
}
@ -1152,16 +1154,13 @@ export class CoreDomUtilsProvider {
/**
* Search for an input with error (core-input-error directive) and scrolls to it if found.
*
* @param container The element that contains the element that must be scrolled.
* @param content The content that must be scrolled.
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll.
* @return True if the element is found, false otherwise.
*/
async scrollToInputError(content?: IonContent, scrollParentClass?: string): Promise<boolean> {
if (!content) {
return false;
}
return this.scrollToElementBySelector(content, '.core-input-error', scrollParentClass);
scrollToInputError(container: HTMLElement | null, content?: IonContent, scrollParentClass?: string): boolean {
return this.scrollToElementBySelector(container, content, '.core-input-error', scrollParentClass);
}
/**