import {
Component,
Input,
ViewChild,
HostListener,
AfterViewChecked,
ChangeDetectorRef,
ElementRef,
Renderer2
} from '@angular/core';
import { AbstractControl } from '@angular/forms';
@Component({
selector: 'sam-label-wrapper',
templateUrl: 'label-wrapper.template.html',
})
export class LabelWrapper implements AfterViewChecked {
/**
* sets the label text
*/
@Input() public label: string;
/**
* sets the name attribute value
*/
@Input() public name: string;
/**
* sets the hint text
*/
@Input() public hint: string;
/**
* deprecated, toggles the required text
*/
@Input() public required: boolean = false;
/**
* toggles the required text
*/
@Input() public requiredFlag: boolean = false;
/**
* set to false if more/less is not required
*/
@Input() public showFullHint : boolean = false;
/**
* set the error message
*/
@Input() public set errorMessage (message: string) {
this._errorMessage = message;
this.setDescribedByEl();
};
public get errorMessage (): string {
return this._errorMessage;
}
@ViewChild('labelDiv') public labelDiv: ElementRef;
@ViewChild('hintContainer') public hintContainer: ElementRef;
public input: HTMLElement;
public showToggle: boolean = false;
public errorElId: string;
public hintElId: string;
private _errorMessage = '';
private toggleOpen: boolean = false;
private lineSize: number;
private lineLimit: number = 2;
private checkMore = false; // semaphore
constructor(
private cdr: ChangeDetectorRef,
private _rend: Renderer2) { }
public ngOnChanges(c) {
if (!this.checkMore
&& c.hint
&& c.hint.previousValue !== c.hint.currentValue) {
// needs to be open to recalc correctly in
// ngAfterViewChecked
this.showToggle = false;
this.toggleOpen = false;
this.checkMore = true;
this.cdr.detectChanges();
}
}
public ngAfterViewInit() {
this.calcToggle();
if(!this.name){
return;
}
const selector = `#${this.name}`;
let lookup;
try{
lookup =
this.labelDiv.nativeElement
.querySelector(selector);
} catch(exception){
console.error(selector + ' not found in label wrapper setup');
}
if(lookup){
this.input = lookup;
this.setLabelIds();
this.setDescribedByEl();
}
}
public ngAfterViewChecked() {
if (this.checkMore && !this.showToggle) {
this.calcToggle();
this.cdr.detectChanges();
this.checkMore = false;
}
}
public calcToggle() {
if (this.hintContainer) {
const numOfLines =
this.calculateNumberOfLines(
this.hintContainer.nativeElement
);
this.showToggle = numOfLines > this.lineLimit
? true
: false;
}
}
public setInputLabelElement (elRefId) {
if (this.input) {
if (elRefId) {
this._rend.setAttribute(
this.input,
'aria-describedby',
elRefId
);
} else {
this._rend.removeAttribute(
this.input,
'aria-describedby'
);
}
}
}
@HostListener('window:resize', ['$event'])
public onResize(event) {
// needs to be open to recalc correctly in
// ngAfterViewChecked
this.showToggle = false;
this.toggleOpen = false;
this.checkMore = true;
this.cdr.detectChanges();
}
public toggleHint(status) {
this.toggleOpen = !status;
}
public calculateNumberOfLines (obj) {
if (!this.lineSize) {
const other = obj.cloneNode(true);
other.innerHTML = 'a
b';
other.style.visibility = 'hidden';
const el = document
.getElementsByTagName('body')[0];
el.appendChild(other);
this.lineSize = other.offsetHeight / 2;
el.removeChild(other);
}
const val = Math.floor(obj.offsetHeight / this.lineSize);
return val;
}
public formatErrors(control: AbstractControl) {
if (!control) {
return;
}
if (control.pristine) {
this.errorMessage = '';
return;
}
if (control.invalid && control.errors) {
for (const k in control.errors) {
const errorObject = control.errors[k];
if (errorObject.message) {
if (Object.prototype.toString.call(errorObject.message)
=== '[object String]') {
this.errorMessage = errorObject.message;
return;
}
}
}
for (const k in control.errors) {
const errorObject = control.errors[k];
this.setInvalidErrors(k, errorObject);
}
// this.errorMessage = 'Invalid';
} else if (!control.errors) {
this.errorMessage = '';
}
}
public clearError() {
this.errorMessage = '';
}
public setOverflow (): string {
return this.showToggle
&& !this.showFullHint
&& !this.toggleOpen
? 'hidden'
: '';
}
public setHeight (): string {
return this.showToggle
&& !this.showFullHint
&& !this.toggleOpen
? '2.88em'
: '';
}
private setInvalidErrors(error, errorObject) {
switch (error) {
case 'maxlength':
const actualLength = errorObject.actualLength;
const requiredLength = errorObject.requiredLength;
this.errorMessage = actualLength
+ ' characters input but max length is '
+ requiredLength;
return;
case 'required':
this.errorMessage = 'This field is required';
return;
case 'isNotBeforeToday':
this.errorMessage = 'Date must not be before today';
return;
default:
return this.errorMessage = 'Invalid';
}
}
private setLabelIds () {
this.setErrorLabelId();
this.setHintLabelId();
}
private setErrorLabelId () {
this.errorElId = this.name + '-error';
this.cdr.detectChanges();
}
private setHintLabelId () {
if (this.hint) {
this.hintElId = this.name + '-hint';
this.cdr.detectChanges();
}
}
private setDescribedByEl () {
if (this.errorMessage) {
this.setInputLabelElement(this.errorElId);
} else if (this.hint) {
this.setInputLabelElement(this.hintElId);
} else {
this.setInputLabelElement('');
}
}
}