MOBILE-3565 components: Create input-errors, mark-required and recaptcha
parent
51e220f497
commit
232669855a
|
@ -19,7 +19,11 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { CoreIconComponent } from './icon/icon';
|
import { CoreIconComponent } from './icon/icon';
|
||||||
import { CoreIframeComponent } from './iframe/iframe';
|
import { CoreIframeComponent } from './iframe/iframe';
|
||||||
|
import { CoreInputErrorsComponent } from './input-errors/input-errors';
|
||||||
import { CoreLoadingComponent } from './loading/loading';
|
import { CoreLoadingComponent } from './loading/loading';
|
||||||
|
import { CoreMarkRequiredComponent } from './mark-required/mark-required';
|
||||||
|
import { CoreRecaptchaComponent } from './recaptcha/recaptcha';
|
||||||
|
import { CoreRecaptchaModalComponent } from './recaptcha/recaptchamodal';
|
||||||
import { CoreShowPasswordComponent } from './show-password/show-password';
|
import { CoreShowPasswordComponent } from './show-password/show-password';
|
||||||
import { CoreEmptyBoxComponent } from './empty-box/empty-box';
|
import { CoreEmptyBoxComponent } from './empty-box/empty-box';
|
||||||
import { CoreDirectivesModule } from '@app/directives/directives.module';
|
import { CoreDirectivesModule } from '@app/directives/directives.module';
|
||||||
|
@ -29,7 +33,11 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreIconComponent,
|
CoreIconComponent,
|
||||||
CoreIframeComponent,
|
CoreIframeComponent,
|
||||||
|
CoreInputErrorsComponent,
|
||||||
CoreLoadingComponent,
|
CoreLoadingComponent,
|
||||||
|
CoreMarkRequiredComponent,
|
||||||
|
CoreRecaptchaComponent,
|
||||||
|
CoreRecaptchaModalComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
CoreEmptyBoxComponent,
|
CoreEmptyBoxComponent,
|
||||||
],
|
],
|
||||||
|
@ -43,7 +51,11 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
|
||||||
exports: [
|
exports: [
|
||||||
CoreIconComponent,
|
CoreIconComponent,
|
||||||
CoreIframeComponent,
|
CoreIframeComponent,
|
||||||
|
CoreInputErrorsComponent,
|
||||||
CoreLoadingComponent,
|
CoreLoadingComponent,
|
||||||
|
CoreMarkRequiredComponent,
|
||||||
|
CoreRecaptchaComponent,
|
||||||
|
CoreRecaptchaModalComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
CoreEmptyBoxComponent,
|
CoreEmptyBoxComponent,
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<div class="core-input-error-container" role="alert" *ngIf="(control && control.dirty && !control.valid) || errorText">
|
||||||
|
<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[error]">{{errorMessages[error]}}</span>
|
||||||
|
<span *ngIf="!errorMessages[error] && error == 'max' && control.errors.max">
|
||||||
|
{{ 'core.login.invalidvaluemax' | translate:{$a: control.errors.max.max} }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="!errorMessages[error] && error == 'min' && control.errors.min">
|
||||||
|
{{ 'core.login.invalidvaluemin' | translate:{$a: control.errors.min.min} }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<div *ngIf="errorText" class="core-input-error" aria-live="assertive">{{ errorText }}</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,16 @@
|
||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.core-input-error-container {
|
||||||
|
.core-input-error {
|
||||||
|
padding: 4px;
|
||||||
|
color: var(--ion-color-danger);
|
||||||
|
font-size: 12px;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, Input, OnChanges, SimpleChange } from '@angular/core';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
import { Translate } from '@singletons/core.singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to show errors if an input isn't valid.
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* The purpose of this component is to make easier and consistent the validation of forms.
|
||||||
|
*
|
||||||
|
* It should be applied next to the input element (ion-input, ion-select, ...). In case of ion-checkbox, it should be in another
|
||||||
|
* item, placing it in the same item as the checkbox will cause problems.
|
||||||
|
*
|
||||||
|
* Please notice that the inputs need to have a FormControl to make it work. That FormControl needs to be passed to this component.
|
||||||
|
*
|
||||||
|
* If this component is placed in the same ion-item as a ion-label or ion-input, then it should have the attribute "item-content",
|
||||||
|
* otherwise Ionic will remove it.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* <ion-item 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>
|
||||||
|
* <core-input-errors item-content [control]="myForm.controls.username" [errorMessages]="usernameErrors"></core-input-errors>
|
||||||
|
* </ion-item>
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-input-errors',
|
||||||
|
templateUrl: 'core-input-errors.html',
|
||||||
|
styleUrls: ['input-errors.scss'],
|
||||||
|
})
|
||||||
|
export class CoreInputErrorsComponent implements OnChanges {
|
||||||
|
|
||||||
|
@Input() control?: FormControl;
|
||||||
|
@Input() errorMessages?: Record<string, string>;
|
||||||
|
@Input() errorText?: string; // Set other non automatic errors.
|
||||||
|
errorKeys: string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize some common errors if they aren't set.
|
||||||
|
*/
|
||||||
|
protected initErrorMessages(): void {
|
||||||
|
this.errorMessages = this.errorMessages || {};
|
||||||
|
|
||||||
|
this.errorMessages.required = this.errorMessages.required || Translate.instance.instant('core.required');
|
||||||
|
this.errorMessages.email = this.errorMessages.email || Translate.instance.instant('core.login.invalidemail');
|
||||||
|
this.errorMessages.date = this.errorMessages.date || Translate.instance.instant('core.login.invaliddate');
|
||||||
|
this.errorMessages.datetime = this.errorMessages.datetime || Translate.instance.instant('core.login.invaliddate');
|
||||||
|
this.errorMessages.datetimelocal = this.errorMessages.datetimelocal || Translate.instance.instant('core.login.invaliddate');
|
||||||
|
this.errorMessages.time = this.errorMessages.time || Translate.instance.instant('core.login.invalidtime');
|
||||||
|
this.errorMessages.url = this.errorMessages.url || Translate.instance.instant('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 || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being changed.
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<ng-content></ng-content>
|
||||||
|
<ion-icon *ngIf="coreMarkRequired" class="core-input-required-asterisk" name="fas-asterisk" color="danger" [title]="requiredLabel">
|
||||||
|
</ion-icon>
|
|
@ -0,0 +1,8 @@
|
||||||
|
:host {
|
||||||
|
.core-input-required-asterisk {
|
||||||
|
font-size: 8px;
|
||||||
|
--padding-start: 4px;
|
||||||
|
line-height: 100%;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, Input, OnInit, AfterViewInit, ElementRef } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { Translate } from '@singletons/core.singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directive to add a red asterisk for required input fields.
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* For forms with required and not required fields, it is recommended to use this directive to mark the required ones.
|
||||||
|
*
|
||||||
|
* This directive should be applied in the label. Example:
|
||||||
|
*
|
||||||
|
* <ion-label core-mark-required="{{field.required}}">{{ 'core.login.username' | translate }}</ion-label>
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: '[core-mark-required]',
|
||||||
|
templateUrl: 'core-mark-required.html',
|
||||||
|
styleUrls: ['mark-required.scss'],
|
||||||
|
})
|
||||||
|
export class CoreMarkRequiredComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
|
// eslint-disable-next-line @angular-eslint/no-input-rename
|
||||||
|
@Input('core-mark-required') coreMarkRequired: boolean | string = true;
|
||||||
|
|
||||||
|
protected element: HTMLElement;
|
||||||
|
requiredLabel?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
element: ElementRef,
|
||||||
|
) {
|
||||||
|
this.element = element.nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.requiredLabel = Translate.instance.instant('core.required');
|
||||||
|
this.coreMarkRequired = CoreUtils.instance.isTrueOrOne(this.coreMarkRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the view is initialized.
|
||||||
|
*/
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
if (this.coreMarkRequired) {
|
||||||
|
// Add the "required" to the aria-label.
|
||||||
|
const ariaLabel = this.element.getAttribute('aria-label') ||
|
||||||
|
CoreTextUtils.instance.cleanTags(this.element.innerHTML, true);
|
||||||
|
if (ariaLabel) {
|
||||||
|
this.element.setAttribute('aria-label', ariaLabel + ' ' + this.requiredLabel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove the "required" from the aria-label.
|
||||||
|
const ariaLabel = this.element.getAttribute('aria-label');
|
||||||
|
if (ariaLabel) {
|
||||||
|
this.element.setAttribute('aria-label', ariaLabel.replace(' ' + this.requiredLabel, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!-- ReCAPTCHA V2 -->
|
||||||
|
<div *ngIf="publicKey && siteUrl && model">
|
||||||
|
<!-- A button to open the recaptcha modal. -->
|
||||||
|
<ion-button expand="block" color="light" class="ion-margin" *ngIf="!model[modelValueName]" (click)="answerRecaptcha()">
|
||||||
|
{{ 'core.resourcedisplayopen' | translate }}
|
||||||
|
</ion-button>
|
||||||
|
<ion-item *ngIf="model[modelValueName]">
|
||||||
|
<ion-label color="success">{{ 'core.answered' | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="expired">
|
||||||
|
<ion-label color="danger">{{ 'core.login.recaptchaexpired' | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>{{ 'core.login.security_question' | translate }}</ion-title>
|
||||||
|
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon slot="icon-only" name="fa-times"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<core-iframe [src]="recaptchaUrl" (loaded)="loaded($event)"></core-iframe>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,85 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreLang } from '@services/lang';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { ModalController } from '@singletons/core.singletons';
|
||||||
|
import { CoreRecaptchaModalComponent } from './recaptchamodal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that allows answering a recaptcha.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-recaptcha',
|
||||||
|
templateUrl: 'core-recaptcha.html',
|
||||||
|
})
|
||||||
|
export class CoreRecaptchaComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() model?: Record<string, string>; // The model where to store the recaptcha response.
|
||||||
|
@Input() publicKey?: string; // The site public key.
|
||||||
|
@Input() modelValueName = 'recaptcharesponse'; // Name of the model property where to store the response.
|
||||||
|
@Input() siteUrl?: string; // The site URL. If not defined, current site.
|
||||||
|
|
||||||
|
expired = false;
|
||||||
|
|
||||||
|
protected lang?: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initLang();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.siteUrl = this.siteUrl || CoreSites.instance.getCurrentSite()?.getURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the lang property.
|
||||||
|
*/
|
||||||
|
protected async initLang(): Promise<void> {
|
||||||
|
this.lang = await CoreLang.instance.getCurrentLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the recaptcha modal.
|
||||||
|
*/
|
||||||
|
async answerRecaptcha(): Promise<void> {
|
||||||
|
// Set the iframe src. We use an iframe because reCaptcha V2 doesn't work with file:// protocol.
|
||||||
|
const src = CoreTextUtils.instance.concatenatePaths(this.siteUrl!, 'webservice/recaptcha.php?lang=' + this.lang);
|
||||||
|
|
||||||
|
// Modal to answer the recaptcha.
|
||||||
|
// This is because the size of the recaptcha is dynamic, so it could cause problems if it was displayed inline.
|
||||||
|
|
||||||
|
const modal = await ModalController.instance.create({
|
||||||
|
component: CoreRecaptchaModalComponent,
|
||||||
|
cssClass: 'core-modal-fullscreen',
|
||||||
|
componentProps: {
|
||||||
|
recaptchaUrl: src,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await modal.present();
|
||||||
|
|
||||||
|
const result = await modal.onWillDismiss();
|
||||||
|
|
||||||
|
this.expired = result.data.expired;
|
||||||
|
this.model![this.modelValueName] = result.data.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, Input, OnDestroy } from '@angular/core';
|
||||||
|
|
||||||
|
import { ModalController } from '@singletons/core.singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to display a the recaptcha in a modal.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-recaptcha-modal',
|
||||||
|
templateUrl: 'core-recaptchamodal.html',
|
||||||
|
})
|
||||||
|
export class CoreRecaptchaModalComponent implements OnDestroy {
|
||||||
|
|
||||||
|
@Input() recaptchaUrl?: string;
|
||||||
|
|
||||||
|
expired = false;
|
||||||
|
value = '';
|
||||||
|
|
||||||
|
protected messageListenerFunction: (event: MessageEvent) => Promise<void>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Listen for messages from the iframe.
|
||||||
|
this.messageListenerFunction = this.onIframeMessage.bind(this);
|
||||||
|
window.addEventListener('message', this.messageListenerFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close modal.
|
||||||
|
*/
|
||||||
|
closeModal(): void {
|
||||||
|
ModalController.instance.dismiss({
|
||||||
|
expired: this.expired,
|
||||||
|
value: this.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The iframe with the recaptcha was loaded.
|
||||||
|
*
|
||||||
|
* @param iframe Iframe element.
|
||||||
|
*/
|
||||||
|
loaded(iframe: HTMLIFrameElement): void {
|
||||||
|
// Search the iframe content.
|
||||||
|
const contentWindow = iframe?.contentWindow;
|
||||||
|
|
||||||
|
if (contentWindow) {
|
||||||
|
try {
|
||||||
|
// Set the callbacks we're interested in.
|
||||||
|
contentWindow['recaptchacallback'] = this.onRecaptchaCallback.bind(this);
|
||||||
|
contentWindow['recaptchaexpiredcallback'] = this.onRecaptchaExpiredCallback.bind(this);
|
||||||
|
} catch (error) {
|
||||||
|
// Cannot access the window.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Treat an iframe message event.
|
||||||
|
*
|
||||||
|
* @param event Event.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async onIframeMessage(event: MessageEvent): Promise<void> {
|
||||||
|
if (!event.data || event.data.environment != 'moodleapp' || event.data.context != 'recaptcha') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.data.action) {
|
||||||
|
case 'callback':
|
||||||
|
this.onRecaptchaCallback(event.data.value);
|
||||||
|
break;
|
||||||
|
case 'expired':
|
||||||
|
this.onRecaptchaExpiredCallback();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recapcha callback called.
|
||||||
|
*
|
||||||
|
* @param value Value received.
|
||||||
|
*/
|
||||||
|
protected onRecaptchaCallback(value: string): void {
|
||||||
|
this.expired = false;
|
||||||
|
this.value = value;
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recapcha expired callback called.
|
||||||
|
*/
|
||||||
|
protected onRecaptchaExpiredCallback(): void {
|
||||||
|
this.expired = true;
|
||||||
|
this.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
window.removeEventListener('message', this.messageListenerFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue