MOBILE-3947 core: Avoid item overflow on input errors
parent
be7f86edd2
commit
bea73940ed
|
@ -21,7 +21,7 @@
|
|||
formControlName="name">
|
||||
<div slot="label" [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</div>
|
||||
</ion-input>
|
||||
<core-input-errors [control]="form.controls.name" [errorMessages]="errors" />
|
||||
<core-input-errors [control]="form.controls.name" />
|
||||
</ion-item>
|
||||
|
||||
<!-- Date. -->
|
||||
|
@ -37,7 +37,7 @@
|
|||
</ion-datetime>
|
||||
</ng-template>
|
||||
</ion-modal>
|
||||
<core-input-errors [control]="form.controls.timestart" [errorMessages]="errors" />
|
||||
<core-input-errors [control]="form.controls.timestart" />
|
||||
</ion-item>
|
||||
|
||||
<!-- Type. -->
|
||||
|
|
|
@ -69,7 +69,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
groups: CoreGroup[] = [];
|
||||
loadingGroups = false;
|
||||
courseGroupSet = false;
|
||||
errors: Record<string, string>;
|
||||
error = false;
|
||||
eventRepeatId?: number;
|
||||
otherEventsCount = 0;
|
||||
|
@ -100,9 +99,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
|||
) {
|
||||
this.currentSite = CoreSites.getRequiredCurrentSite();
|
||||
this.remindersEnabled = CoreReminders.isEnabled();
|
||||
this.errors = {
|
||||
required: Translate.instant('core.required'),
|
||||
};
|
||||
|
||||
this.form = new FormGroup({});
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<ng-container *ngIf="control && control.dirty && !control.valid">
|
||||
<ng-container *ngFor="let error of errorKeys">
|
||||
<div *ngIf="control.hasError(error)" class="core-input-error">
|
||||
<span *ngIf="errorMessages && errorMessages[error]">{{errorMessages[error]}}</span>
|
||||
<span *ngIf="errorMessages && errorMessages[error]">{{ errorMessages[error] | translate }}</span>
|
||||
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'max' && control.errors?.max">
|
||||
{{ 'core.login.invalidvaluemax' | translate:{$a: control.errors!.max.max} }}
|
||||
</span>
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Translate } from '@singletons';
|
||||
|
||||
/**
|
||||
* Component to show errors if an input isn't valid.
|
||||
|
@ -30,8 +29,7 @@ import { Translate } from '@singletons';
|
|||
* Example usage:
|
||||
*
|
||||
* <ion-item class="ion-text-wrap">
|
||||
* <ion-label stacked [core-mark-required]="true">{{ 'core.login.username' | translate }}</ion-label>
|
||||
* <ion-input type="text" name="username" formControlName="username"></ion-input>
|
||||
* <ion-input type="text" name="username" formControlName="username" required="true"></ion-input>
|
||||
* <core-input-errors [control]="myForm.controls.username" [errorMessages]="usernameErrors"></core-input-errors>
|
||||
* </ion-item>
|
||||
*/
|
||||
|
@ -42,12 +40,12 @@ import { Translate } from '@singletons';
|
|||
})
|
||||
export class CoreInputErrorsComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input() control?: FormControl;
|
||||
@Input() errorMessages: Record<string, string> = {};
|
||||
@Input() control?: FormControl; // Needed to be able to check the validity of the input.
|
||||
@Input() errorMessages: Record<string, string> = {}; // Error messages to show. Keys must be the name of the error.
|
||||
@Input() errorText = ''; // Set other non automatic errors.
|
||||
errorKeys: string[] = [];
|
||||
|
||||
protected element: HTMLElement;
|
||||
protected hostElement: HTMLElement;
|
||||
|
||||
@HostBinding('class.has-errors')
|
||||
get hasErrors(): boolean {
|
||||
|
@ -59,31 +57,60 @@ export class CoreInputErrorsComponent implements OnInit, OnChanges {
|
|||
constructor(
|
||||
element: ElementRef,
|
||||
) {
|
||||
this.element = element.nativeElement;
|
||||
this.hostElement = element.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize some common errors if they aren't set.
|
||||
*/
|
||||
protected initErrorMessages(): void {
|
||||
this.errorMessages.required = this.errorMessages.required || Translate.instant('core.required');
|
||||
this.errorMessages.email = this.errorMessages.email || Translate.instant('core.login.invalidemail');
|
||||
this.errorMessages.date = this.errorMessages.date || Translate.instant('core.login.invaliddate');
|
||||
this.errorMessages.datetime = this.errorMessages.datetime || Translate.instant('core.login.invaliddate');
|
||||
this.errorMessages.datetimelocal = this.errorMessages.datetimelocal || Translate.instant('core.login.invaliddate');
|
||||
this.errorMessages.time = this.errorMessages.time || Translate.instant('core.login.invalidtime');
|
||||
this.errorMessages.url = this.errorMessages.url || Translate.instant('core.login.invalidurl');
|
||||
|
||||
this.errorMessages = {
|
||||
required: this.errorMessages.required || 'core.required',
|
||||
email: this.errorMessages.email || 'core.login.invalidemail',
|
||||
date: this.errorMessages.date || 'core.login.invaliddate',
|
||||
datetime: this.errorMessages.datetime || 'core.login.invaliddate',
|
||||
datetimelocal: this.errorMessages.datetimelocal || 'core.login.invaliddate',
|
||||
time: this.errorMessages.time || 'core.login.invalidtime',
|
||||
url: this.errorMessages.url || 'core.login.invalidurl',
|
||||
// Set empty values by default, the default error messages will be built in the template when needed.
|
||||
this.errorMessages.max = this.errorMessages.max || '';
|
||||
this.errorMessages.min = this.errorMessages.min || '';
|
||||
max: this.errorMessages.max || '',
|
||||
min: this.errorMessages.min || '',
|
||||
};
|
||||
|
||||
this.errorMessages.requiredTrue = this.errorMessages.required;
|
||||
|
||||
this.errorKeys = Object.keys(this.errorMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.element.closest('ion-item')?.classList.add('has-core-input-errors');
|
||||
const parent = this.hostElement.parentElement;
|
||||
let item: HTMLElement | null = null;
|
||||
|
||||
if (parent?.tagName === 'ION-ITEM') {
|
||||
item = parent;
|
||||
|
||||
// Get all elements on the parent and wrap them with a div.
|
||||
// This is needed because otherwise the error message will be shown on the right of the input. Or overflowing the item.
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
wrapper.classList.add('core-input-errors-wrapper');
|
||||
|
||||
Array.from(parent.children).forEach((child) => {
|
||||
if (!child.slot) {
|
||||
wrapper.appendChild(child);
|
||||
}
|
||||
});
|
||||
|
||||
parent.appendChild(wrapper);
|
||||
} else {
|
||||
item = this.hostElement.closest('ion-item');
|
||||
}
|
||||
|
||||
item?.classList.add('has-core-input-errors');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,11 +119,6 @@ export class CoreInputErrorsComponent implements OnInit, OnChanges {
|
|||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if ((changes.control || changes.errorMessages) && this.control) {
|
||||
this.initErrorMessages();
|
||||
|
||||
this.errorKeys = this.errorMessages ? Object.keys(this.errorMessages) : [];
|
||||
}
|
||||
if (changes.errorText) {
|
||||
this.errorText = changes.errorText.currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,13 +37,13 @@ export class CoreMarkRequiredComponent implements OnInit, AfterViewInit {
|
|||
|
||||
@Input('core-mark-required') coreMarkRequired: boolean | string = true;
|
||||
|
||||
protected element: HTMLElement;
|
||||
protected hostElement: HTMLElement;
|
||||
requiredLabel = Translate.instant('core.required');
|
||||
|
||||
constructor(
|
||||
element: ElementRef,
|
||||
) {
|
||||
this.element = element.nativeElement;
|
||||
this.hostElement = element.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,18 +59,21 @@ export class CoreMarkRequiredComponent implements OnInit, AfterViewInit {
|
|||
ngAfterViewInit(): void {
|
||||
if (this.coreMarkRequired) {
|
||||
// Add the "required" to the aria-label.
|
||||
const ariaLabel = this.element.getAttribute('aria-label') ||
|
||||
CoreTextUtils.cleanTags(this.element.innerHTML, { singleLine: true });
|
||||
const ariaLabel = this.hostElement.getAttribute('aria-label') ||
|
||||
CoreTextUtils.cleanTags(this.hostElement.innerHTML, { singleLine: true });
|
||||
if (ariaLabel) {
|
||||
this.element.setAttribute('aria-label', ariaLabel + ' ' + this.requiredLabel);
|
||||
this.hostElement.setAttribute('aria-label', ariaLabel + '. ' + this.requiredLabel);
|
||||
}
|
||||
} else {
|
||||
// Remove the "required" from the aria-label.
|
||||
const ariaLabel = this.element.getAttribute('aria-label');
|
||||
const ariaLabel = this.hostElement.getAttribute('aria-label');
|
||||
if (ariaLabel) {
|
||||
this.element.setAttribute('aria-label', ariaLabel.replace(' ' + this.requiredLabel, ''));
|
||||
}
|
||||
this.hostElement.setAttribute('aria-label', ariaLabel.replace('. ' + this.requiredLabel, ''));
|
||||
}
|
||||
}
|
||||
|
||||
const input = this.hostElement.closest('ion-input, ion-textarea');
|
||||
input?.setAttribute('required', this.coreMarkRequired ? 'true' : 'false');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -105,15 +105,14 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
});
|
||||
|
||||
// Setup validation errors.
|
||||
this.usernameErrors = CoreLoginHelper.getErrorMessages('core.login.usernamerequired');
|
||||
this.passwordErrors = CoreLoginHelper.getErrorMessages('core.login.passwordrequired');
|
||||
this.emailErrors = CoreLoginHelper.getErrorMessages('core.login.missingemail');
|
||||
this.policyErrors = CoreLoginHelper.getErrorMessages('core.login.policyagree');
|
||||
this.email2Errors = CoreLoginHelper.getErrorMessages(
|
||||
'core.login.missingemail',
|
||||
undefined,
|
||||
'core.login.emailnotmatch',
|
||||
);
|
||||
this.usernameErrors = { required: 'core.login.usernamerequired' };
|
||||
this.passwordErrors = { required: 'core.login.passwordrequired' };
|
||||
this.emailErrors = { required: 'core.login.missingemail' };
|
||||
this.policyErrors = { required: 'core.login.policyagree' };
|
||||
this.email2Errors = {
|
||||
required: 'core.login.missingemail',
|
||||
pattern: 'core.login.emailnotmatch',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -224,7 +223,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
|||
const namefieldsErrors = {};
|
||||
if (this.settings.namefields) {
|
||||
this.settings.namefields.forEach((field) => {
|
||||
namefieldsErrors[field] = CoreLoginHelper.getErrorMessages('core.login.missing' + field);
|
||||
namefieldsErrors[field] = { required: 'core.login.missing' + field };
|
||||
});
|
||||
}
|
||||
this.namefieldsErrors = namefieldsErrors;
|
||||
|
|
|
@ -252,60 +252,6 @@ export class CoreLoginHelperProvider {
|
|||
return CoreTextUtils.treatDisabledFeatures(disabledFeatures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an object with error messages for some common errors.
|
||||
* Please notice that this function doesn't support all possible error types.
|
||||
*
|
||||
* @param requiredMsg Code of the string for required error.
|
||||
* @param emailMsg Code of the string for invalid email error.
|
||||
* @param patternMsg Code of the string for pattern not match error.
|
||||
* @param urlMsg Code of the string for invalid url error.
|
||||
* @param minlengthMsg Code of the string for "too short" error.
|
||||
* @param maxlengthMsg Code of the string for "too long" error.
|
||||
* @param minMsg Code of the string for min value error.
|
||||
* @param maxMsg Code of the string for max value error.
|
||||
* @returns Object with the errors.
|
||||
*/
|
||||
getErrorMessages(
|
||||
requiredMsg?: string,
|
||||
emailMsg?: string,
|
||||
patternMsg?: string,
|
||||
urlMsg?: string,
|
||||
minlengthMsg?: string,
|
||||
maxlengthMsg?: string,
|
||||
minMsg?: string,
|
||||
maxMsg?: string,
|
||||
): Record<string, string> {
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
if (requiredMsg) {
|
||||
errors.required = errors.requiredTrue = Translate.instant(requiredMsg);
|
||||
}
|
||||
if (emailMsg) {
|
||||
errors.email = Translate.instant(emailMsg);
|
||||
}
|
||||
if (patternMsg) {
|
||||
errors.pattern = Translate.instant(patternMsg);
|
||||
}
|
||||
if (urlMsg) {
|
||||
errors.url = Translate.instant(urlMsg);
|
||||
}
|
||||
if (minlengthMsg) {
|
||||
errors.minlength = Translate.instant(minlengthMsg);
|
||||
}
|
||||
if (maxlengthMsg) {
|
||||
errors.maxlength = Translate.instant(maxlengthMsg);
|
||||
}
|
||||
if (minMsg) {
|
||||
errors.min = Translate.instant(minMsg);
|
||||
}
|
||||
if (maxMsg) {
|
||||
errors.max = Translate.instant(maxMsg);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logo URL from a site public config.
|
||||
*
|
||||
|
|
|
@ -126,6 +126,10 @@ ion-item > .in-item,
|
|||
white-space: normal !important;
|
||||
}
|
||||
|
||||
ion-item .core-input-errors-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@each $color-name, $unused in $colors {
|
||||
.text-#{$color-name},
|
||||
p.text-#{$color-name} {
|
||||
|
|
|
@ -10,6 +10,7 @@ For more information about upgrading, read the official documentation: https://m
|
|||
- Removed CoreToLocaleStringPipe deprecated since 3.6.0
|
||||
- With the upgrade to Ionic 7 ion-slides is no longer supported and now you need to use swiper-container and swiper-slide. More info here: https://ionicframework.com/docs/angular/slides
|
||||
- With the upgrade to Ionic7 ion-datetime has changed its usage. We recommend using ion-datetime-button. More info here: https://ionicframework.com/docs/updating/6-0#datetime
|
||||
- CoreLoginHelper.getErrorMessages has been removed. Please create the messages object yourself.
|
||||
|
||||
=== 4.3.0 ===
|
||||
|
||||
|
|
Loading…
Reference in New Issue