MOBILE-3947 core: Avoid item overflow on input errors
parent
be7f86edd2
commit
bea73940ed
|
@ -21,7 +21,7 @@
|
||||||
formControlName="name">
|
formControlName="name">
|
||||||
<div slot="label" [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</div>
|
<div slot="label" [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</div>
|
||||||
</ion-input>
|
</ion-input>
|
||||||
<core-input-errors [control]="form.controls.name" [errorMessages]="errors" />
|
<core-input-errors [control]="form.controls.name" />
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- Date. -->
|
<!-- Date. -->
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
</ion-datetime>
|
</ion-datetime>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ion-modal>
|
</ion-modal>
|
||||||
<core-input-errors [control]="form.controls.timestart" [errorMessages]="errors" />
|
<core-input-errors [control]="form.controls.timestart" />
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- Type. -->
|
<!-- Type. -->
|
||||||
|
|
|
@ -69,7 +69,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
||||||
groups: CoreGroup[] = [];
|
groups: CoreGroup[] = [];
|
||||||
loadingGroups = false;
|
loadingGroups = false;
|
||||||
courseGroupSet = false;
|
courseGroupSet = false;
|
||||||
errors: Record<string, string>;
|
|
||||||
error = false;
|
error = false;
|
||||||
eventRepeatId?: number;
|
eventRepeatId?: number;
|
||||||
otherEventsCount = 0;
|
otherEventsCount = 0;
|
||||||
|
@ -100,9 +99,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
|
||||||
) {
|
) {
|
||||||
this.currentSite = CoreSites.getRequiredCurrentSite();
|
this.currentSite = CoreSites.getRequiredCurrentSite();
|
||||||
this.remindersEnabled = CoreReminders.isEnabled();
|
this.remindersEnabled = CoreReminders.isEnabled();
|
||||||
this.errors = {
|
|
||||||
required: Translate.instant('core.required'),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.form = new FormGroup({});
|
this.form = new FormGroup({});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<ng-container *ngIf="control && control.dirty && !control.valid">
|
<ng-container *ngIf="control && control.dirty && !control.valid">
|
||||||
<ng-container *ngFor="let error of errorKeys">
|
<ng-container *ngFor="let error of errorKeys">
|
||||||
<div *ngIf="control.hasError(error)" class="core-input-error">
|
<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">
|
<span *ngIf="(!errorMessages || !errorMessages[error]) && error === 'max' && control.errors?.max">
|
||||||
{{ 'core.login.invalidvaluemax' | translate:{$a: control.errors!.max.max} }}
|
{{ 'core.login.invalidvaluemax' | translate:{$a: control.errors!.max.max} }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange } from '@angular/core';
|
import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange } from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
import { Translate } from '@singletons';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to show errors if an input isn't valid.
|
* Component to show errors if an input isn't valid.
|
||||||
|
@ -30,8 +29,7 @@ import { Translate } from '@singletons';
|
||||||
* Example usage:
|
* Example usage:
|
||||||
*
|
*
|
||||||
* <ion-item class="ion-text-wrap">
|
* <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" required="true"></ion-input>
|
||||||
* <ion-input type="text" name="username" formControlName="username"></ion-input>
|
|
||||||
* <core-input-errors [control]="myForm.controls.username" [errorMessages]="usernameErrors"></core-input-errors>
|
* <core-input-errors [control]="myForm.controls.username" [errorMessages]="usernameErrors"></core-input-errors>
|
||||||
* </ion-item>
|
* </ion-item>
|
||||||
*/
|
*/
|
||||||
|
@ -42,12 +40,12 @@ import { Translate } from '@singletons';
|
||||||
})
|
})
|
||||||
export class CoreInputErrorsComponent implements OnInit, OnChanges {
|
export class CoreInputErrorsComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
@Input() control?: FormControl;
|
@Input() control?: FormControl; // Needed to be able to check the validity of the input.
|
||||||
@Input() errorMessages: Record<string, string> = {};
|
@Input() errorMessages: Record<string, string> = {}; // Error messages to show. Keys must be the name of the error.
|
||||||
@Input() errorText = ''; // Set other non automatic errors.
|
@Input() errorText = ''; // Set other non automatic errors.
|
||||||
errorKeys: string[] = [];
|
errorKeys: string[] = [];
|
||||||
|
|
||||||
protected element: HTMLElement;
|
protected hostElement: HTMLElement;
|
||||||
|
|
||||||
@HostBinding('class.has-errors')
|
@HostBinding('class.has-errors')
|
||||||
get hasErrors(): boolean {
|
get hasErrors(): boolean {
|
||||||
|
@ -59,31 +57,60 @@ export class CoreInputErrorsComponent implements OnInit, OnChanges {
|
||||||
constructor(
|
constructor(
|
||||||
element: ElementRef,
|
element: ElementRef,
|
||||||
) {
|
) {
|
||||||
this.element = element.nativeElement;
|
this.hostElement = element.nativeElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize some common errors if they aren't set.
|
* Initialize some common errors if they aren't set.
|
||||||
*/
|
*/
|
||||||
protected initErrorMessages(): void {
|
protected initErrorMessages(): void {
|
||||||
this.errorMessages.required = this.errorMessages.required || Translate.instant('core.required');
|
this.errorMessages = {
|
||||||
this.errorMessages.email = this.errorMessages.email || Translate.instant('core.login.invalidemail');
|
required: this.errorMessages.required || 'core.required',
|
||||||
this.errorMessages.date = this.errorMessages.date || Translate.instant('core.login.invaliddate');
|
email: this.errorMessages.email || 'core.login.invalidemail',
|
||||||
this.errorMessages.datetime = this.errorMessages.datetime || Translate.instant('core.login.invaliddate');
|
date: this.errorMessages.date || 'core.login.invaliddate',
|
||||||
this.errorMessages.datetimelocal = this.errorMessages.datetimelocal || Translate.instant('core.login.invaliddate');
|
datetime: this.errorMessages.datetime || 'core.login.invaliddate',
|
||||||
this.errorMessages.time = this.errorMessages.time || Translate.instant('core.login.invalidtime');
|
datetimelocal: this.errorMessages.datetimelocal || 'core.login.invaliddate',
|
||||||
this.errorMessages.url = this.errorMessages.url || Translate.instant('core.login.invalidurl');
|
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.
|
||||||
|
max: this.errorMessages.max || '',
|
||||||
|
min: this.errorMessages.min || '',
|
||||||
|
};
|
||||||
|
|
||||||
// Set empty values by default, the default error messages will be built in the template when needed.
|
this.errorMessages.requiredTrue = this.errorMessages.required;
|
||||||
this.errorMessages.max = this.errorMessages.max || '';
|
|
||||||
this.errorMessages.min = this.errorMessages.min || '';
|
this.errorKeys = Object.keys(this.errorMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
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 {
|
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||||
if ((changes.control || changes.errorMessages) && this.control) {
|
if ((changes.control || changes.errorMessages) && this.control) {
|
||||||
this.initErrorMessages();
|
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;
|
@Input('core-mark-required') coreMarkRequired: boolean | string = true;
|
||||||
|
|
||||||
protected element: HTMLElement;
|
protected hostElement: HTMLElement;
|
||||||
requiredLabel = Translate.instant('core.required');
|
requiredLabel = Translate.instant('core.required');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
element: ElementRef,
|
element: ElementRef,
|
||||||
) {
|
) {
|
||||||
this.element = element.nativeElement;
|
this.hostElement = element.nativeElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,18 +59,21 @@ export class CoreMarkRequiredComponent implements OnInit, AfterViewInit {
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
if (this.coreMarkRequired) {
|
if (this.coreMarkRequired) {
|
||||||
// Add the "required" to the aria-label.
|
// Add the "required" to the aria-label.
|
||||||
const ariaLabel = this.element.getAttribute('aria-label') ||
|
const ariaLabel = this.hostElement.getAttribute('aria-label') ||
|
||||||
CoreTextUtils.cleanTags(this.element.innerHTML, { singleLine: true });
|
CoreTextUtils.cleanTags(this.hostElement.innerHTML, { singleLine: true });
|
||||||
if (ariaLabel) {
|
if (ariaLabel) {
|
||||||
this.element.setAttribute('aria-label', ariaLabel + ' ' + this.requiredLabel);
|
this.hostElement.setAttribute('aria-label', ariaLabel + '. ' + this.requiredLabel);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Remove the "required" from the aria-label.
|
// Remove the "required" from the aria-label.
|
||||||
const ariaLabel = this.element.getAttribute('aria-label');
|
const ariaLabel = this.hostElement.getAttribute('aria-label');
|
||||||
if (ariaLabel) {
|
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.
|
// Setup validation errors.
|
||||||
this.usernameErrors = CoreLoginHelper.getErrorMessages('core.login.usernamerequired');
|
this.usernameErrors = { required: 'core.login.usernamerequired' };
|
||||||
this.passwordErrors = CoreLoginHelper.getErrorMessages('core.login.passwordrequired');
|
this.passwordErrors = { required: 'core.login.passwordrequired' };
|
||||||
this.emailErrors = CoreLoginHelper.getErrorMessages('core.login.missingemail');
|
this.emailErrors = { required: 'core.login.missingemail' };
|
||||||
this.policyErrors = CoreLoginHelper.getErrorMessages('core.login.policyagree');
|
this.policyErrors = { required: 'core.login.policyagree' };
|
||||||
this.email2Errors = CoreLoginHelper.getErrorMessages(
|
this.email2Errors = {
|
||||||
'core.login.missingemail',
|
required: 'core.login.missingemail',
|
||||||
undefined,
|
pattern: 'core.login.emailnotmatch',
|
||||||
'core.login.emailnotmatch',
|
};
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -224,7 +223,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
|
||||||
const namefieldsErrors = {};
|
const namefieldsErrors = {};
|
||||||
if (this.settings.namefields) {
|
if (this.settings.namefields) {
|
||||||
this.settings.namefields.forEach((field) => {
|
this.settings.namefields.forEach((field) => {
|
||||||
namefieldsErrors[field] = CoreLoginHelper.getErrorMessages('core.login.missing' + field);
|
namefieldsErrors[field] = { required: 'core.login.missing' + field };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.namefieldsErrors = namefieldsErrors;
|
this.namefieldsErrors = namefieldsErrors;
|
||||||
|
|
|
@ -252,60 +252,6 @@ export class CoreLoginHelperProvider {
|
||||||
return CoreTextUtils.treatDisabledFeatures(disabledFeatures);
|
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.
|
* Get logo URL from a site public config.
|
||||||
*
|
*
|
||||||
|
|
|
@ -126,6 +126,10 @@ ion-item > .in-item,
|
||||||
white-space: normal !important;
|
white-space: normal !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-item .core-input-errors-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
@each $color-name, $unused in $colors {
|
@each $color-name, $unused in $colors {
|
||||||
.text-#{$color-name},
|
.text-#{$color-name},
|
||||||
p.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
|
- 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 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
|
- 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 ===
|
=== 4.3.0 ===
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue