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( constructor(
formBuilder: FormBuilder, formBuilder: FormBuilder,
protected elementRef: ElementRef,
) { ) {
// Create an empty form group. The controls will be added by the access rules components. // Create an empty form group. The controls will be added by the access rules components.
this.preflightForm = formBuilder.group({}); this.preflightForm = formBuilder.group({});
@ -115,7 +116,12 @@ export class AddonModQuizPreflightModalComponent implements OnInit {
if (!this.preflightForm.valid) { if (!this.preflightForm.valid) {
// Form not valid. Scroll to the first element with errors. // 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. // Input not found, show an error modal.
CoreDomUtils.instance.showErrorModal('core.errorinvalidform', true); 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. * @param slot Slot of the question to scroll to.
*/ */
protected scrollToQuestion(slot: number): void { protected scrollToQuestion(slot: number): void {
if (this.content) { CoreDomUtils.instance.scrollToElementBySelector(
CoreDomUtils.instance.scrollToElementBySelector(this.content, '#addon-mod_quiz-question-' + slot); 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) == '#') { if (href.charAt(0) == '#') {
// Look for id or name. // Look for id or name.
href = href.substr(1); 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; return;
} }

View File

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

View File

@ -12,7 +12,7 @@
// 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 { 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 { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { IonContent, IonRefresher } from '@ionic/angular'; import { IonContent, IonRefresher } from '@ionic/angular';
@ -81,6 +81,8 @@ export class CoreLoginEmailSignupPage implements OnInit {
constructor( constructor(
protected fb: FormBuilder, protected fb: FormBuilder,
protected elementRef: ElementRef,
protected changeDetector: ChangeDetectorRef,
) { ) {
// Create the ageVerificationForm. // Create the ageVerificationForm.
this.ageVerificationForm = this.fb.group({ this.ageVerificationForm = this.fb.group({
@ -272,8 +274,17 @@ export class CoreLoginEmailSignupPage implements OnInit {
e.stopPropagation(); e.stopPropagation();
if (!this.signupForm.valid || (this.settings?.recaptchapublickey && !this.captcha.recaptcharesponse)) { if (!this.signupForm.valid || (this.settings?.recaptchapublickey && !this.captcha.recaptcharesponse)) {
// Form not valid. Scroll to the first element with errors. // Form not valid. Mark all controls as dirty to display errors.
const errorFound = await CoreDomUtils.instance.scrollToInputError(this.content); 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) { if (!errorFound) {
// Input not found, show an error modal. // 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. * 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 content The content that must be scrolled.
* @param selector Selector to find the element to scroll to. * @param selector Selector to find the element to scroll to.
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll. * @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll.
* @param duration Duration of the scroll animation in milliseconds. * @param duration Duration of the scroll animation in milliseconds.
* @return True if the element is found, false otherwise. * @return True if the element is found, false otherwise.
*/ */
async scrollToElementBySelector( scrollToElementBySelector(
content: IonContent, container: HTMLElement | null,
content: IonContent | undefined,
selector: string, selector: string,
scrollParentClass?: string, scrollParentClass?: string,
duration?: number, duration?: number,
): Promise<boolean> { ): boolean {
// @todo: This function is broken. Scroll element cannot be used because it uses shadow DOM so querySelector returns null. if (!container || !content) {
// Also, traversing using parentElement doesn't work either, offsetParent isn't part of the parentElement tree. return false;
try { }
const scrollElement = await content.getScrollElement();
const position = this.getElementXY(scrollElement, selector, scrollParentClass); try {
const position = this.getElementXY(container, selector, scrollParentClass);
if (!position) { if (!position) {
return false; 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. * 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 content The content that must be scrolled.
* @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll. * @param scrollParentClass Parent class where to stop calculating the position. Default inner-scroll.
* @return True if the element is found, false otherwise. * @return True if the element is found, false otherwise.
*/ */
async scrollToInputError(content?: IonContent, scrollParentClass?: string): Promise<boolean> { scrollToInputError(container: HTMLElement | null, content?: IonContent, scrollParentClass?: string): boolean {
if (!content) { return this.scrollToElementBySelector(container, content, '.core-input-error', scrollParentClass);
return false;
}
return this.scrollToElementBySelector(content, '.core-input-error', scrollParentClass);
} }
/** /**