Reputation: 375
I am looking for a Quarterly date picker using ng-bootstrap.
At the moment I have Month and Year see STACKBLITZ but I would like to change the Month to Quarter.
Is this possible with ng-bootstrap?
For info: here is a previous stackblitz example using Angular Material
Upvotes: 0
Views: 6303
Reputation: 57939
"briefly" explain about the stackblitz
Basicaly we has a ng-dropDown that show a series of ngbDropdownItem
we has as variables
year:number; //the year selected
quarter:string; //hte quarter selected
yearDefault=new Date().getFullYear() //the year by defect
quarterDefault="Q"+(1+Math.floor(new Date().getMonth()/3)) //the quarter by defect
showQuarter:boolean=true; //a boolean variable.
//if true, the ngbDropdownItems will be the quarter,
//else the years
year10:number; //auxliar for show the list of years
And an auxiliar array to show the quarter
options: any[] = [
{value:'Q1',months:['Jan','Feb','Mar']},
{value:'Q2',months:['Apr','May','Jun']},
{value:'Q3',months:['Jul','Aug','Sep']},
{value:'Q4',months:['Oct','Nov','Dec']}
];
So, in our ngbDropdownMenu we can show
<ng-container *ngIf="showQuarter">
<button [ngClass]="{'bg-primary':item.value==quarter}" ngbDropdownItem
*ngFor="let item of options"
(click)="click(item.value,drop)">
<span class="col" *ngFor="let month of item.months" >
{{month}}
</span>
</button>
</ng-container>
//or
<ng-container *ngIf="!showQuarter">
<button [ngClass]="{'bg-primary':(year10+item)==year}" ngbDropdownItem
*ngFor="let item of [0,1,2,3,4,5,6,7,8,9]"
(click)="changeYear(year10+item);showQuarter=true">
<span >{{year10+item}}</span>
</button>
</ng-container>
Moreover, we show a "header" with two buttons (left and rigth arrow) and a span that show or the year or the decade
<div class="selectYear">
<div class="ngb-dp-arrow">
<button class="btn btn-link ngb-dp-arrow-btn" type="button"
(click)="showQuarter?changeYear((year||yearDefault)-1):year10=year10-10">
<span class="ngb-dp-navigation-chevron">
</span>
</button>
</div>
<button type="button" class="btn btn-link" (click)="changeShowQuarter()">
{{showQuarter?year?year:yearDefault:(year10+' - '+(year10+9))}}
</button>
<div class="ngb-dp-arrow right">
<button class="btn btn-link ngb-dp-arrow-btn" type="button"
(click)="showQuarter?changeYear((year||yearDefault)+1):year10=year10+10">
<span class="ngb-dp-navigation-chevron">
</span>
</button>
</div>
</div>
See how, depending the variable "showQuarter" the buttons make one or another action
The functions are simples, again we make take account that at first, year and quarter can has no value, in that case we use yearDefault and QuarterDefault
changeYear(year)
{
this.year=year || this.yearDefault;
this.quarter=this.quarter||this.quarterDefault
this.control.setValue(this.quarter+" "+this.year || this.yearDefault,{emitEvent:false})
}
changeShowQuarter()
{
this.showQuarter=!this.showQuarter
if (!this.showQuarter)
this.year10=this.year?10*Math.floor(this.year/10):10*Math.floor(this.yearDefault/10)
}
click(quarter,drop)
{
this.quarter=quarter;
this.year=this.year||this.yearDefault
this.control.setValue(this.quarter+" "+this.year,{emitEvent:false})
drop.close()
}
And yes, we has a FormControl called control, becaouse we has an input
<input style="text-transform: uppercase" [formControl]="control" placeholder="Qq yyyy" >
control:FormControl= new FormControl()
To control the manually entry of the quarter and year we subscribe to control.valueChanges, to give value to year and quarter only if the length of string is greater or equal to 6
this.control.valueChanges.pipe(
takeWhile(()=>this.alive),
startWith(this.quarter+" "+this.year),
debounceTime(200))
.subscribe((res:string)=>{
// console.log(this.controlID.nativeElement.selectionStart)
if (res)
{
res=res.toUpperCase()
if (res[0]!="Q")
res="Q"+res;
let value=res.replace(/[^Q|0-9]/g, '');
let quarter;
let year;
if (value.length>=2)
quarter=value[0]+value[1]
if (value.length>=6)
{
year=value.substr(2,4)
this.year=+year
this.quarter=quarter;
this.control.setValue((this.quarter+" "+this.year),{emitEvent:false})
}
else
{
this.year=null;
this.quarter=null;
}
}
})
TODO: create a custom form control with the component, revised the .css to improve and remove unnecesary styles
Update convert in a CustomFormControl it's easy, only implements ControlValueAccessor
disabled: boolean = false;
onChange: (_: any) => void;
onTouched: any;
registerOnChange(fn: (_: any) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
writeValue(value: any): void {
if (value) {
this.year = value.year;
this.quarter = value.quarter;
if (this.year && this.quarter)
this.control.setValue(this.quarter + " " + this.year,{emitEvent:false});
}
}
I use an auxiliar function
setValue(data: any) {
if (data && data.quarter && data.year) {
this.control.setValue(data.quarter + " " + data.year, {
emitEvent: false
});
this.onChange({ quarter: data.quarter, year: data.year });
} else {
this.control.setValue(data, { emitEvent: false });
this.onChange(null);
}
}
That call in control.valueChanges subscribe and in click function
I leave in this stackblitz
NOTE: like ng-bootstrap I choose that the return value was an object with the properties year and quarter
Update @Mohan ask about improve the month date picker adding a minQuarter.
To add a minQuarter and maxQuarter it's "only" add two inputs more
@Input() minQuarter={ quarter: "Q1", year: 0 };
@Input() maxQuarter={ quarter: "Q4", year: 9999 }
Well, now we need disabled the buttons to take account this values. There are severals buttons and we need take caraful when disabled it
//arrow left
[disabled]="minQuarter.year>=(showQuarter?year:year10)"
//arrow right
[disabled]="maxQuarter.year<=(showQuarter?year:(year10+10))"
//the quarters
[disabled]="(minQuarter.year==year && item.value<minQuarter.quarter)
||(maxQuarter.year==year && item.value>minQuarter.quarter)"
//the years
[disabled]="((year10+item)<minQuarter.year || (year10+item)>maxQuarter.year)"
more over, we can take accont when we change manually the quarter, force to get the minQuarter or maxQuarter. So in the subscribe to control.valueChanges,
this.control.valueChanges
.pipe(...)
.subscribe((res: string) => {
let quarter = null;
let year = null;
...
//here check the min and max value
if (year)
{
if (year<this.minQuarter.year)
year=this.minQuarter.year
if (year>this.minQuarter.year)
year=this.maxQuarter.year
if (year==this.minQuarter.year && quarter<this.minQuarter.quarter)
quarter=this.minQuarter.quarter;
if (year==this.maxQuarter.year && quarter>this.minQuarter.quarter)
quarter=this.maxQuarter.quarter;
}
....
});
}
If I not missed, it's all. I updated the stackblitz
Upvotes: 2