import {
Component,
Input,
ViewChild,
HostListener,
AfterViewChecked,
ChangeDetectorRef,
ElementRef,
Renderer2,
OnChanges,
AfterViewInit,
inject,
} from "@angular/core";
import { AbstractControl } from "@angular/forms";
@Component({
selector: "sam-label-wrapper",
templateUrl: "label-wrapper.template.html",
standalone: false,
})
export class LabelWrapper
implements AfterViewChecked, OnChanges, AfterViewInit
{
private cdr = inject(ChangeDetectorRef);
private _rend = inject(Renderer2);
/**
* 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", { static: true }) public labelDiv: ElementRef;
@ViewChild("hintContainer", { static: false })
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;
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 {
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")
public onResize() {
// 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("");
}
}
}