"кратко" объясните о stackblitz
В основном у нас есть ng-dropDown, который показывает серию ngbDropdownItem
у нас есть в качестве переменных
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
И вспомогательный массив для отображения квартала
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']}
];
Итак, в нашем ngbDropdownMenu мы можем показать
<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>
Кроме того, мы показываем «заголовок» с двумя кнопками (стрелка влево и вправо) и интервал, показывающий год или десятилетие
<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>
Посмотрите, как в зависимости от переменной "showQuarter" кнопки выполняют то или иное действие
ФункцииПроще говоря, опять же, мы учитываем, что сначала год и квартал могут не иметь значения, в этом случае мы используем yearDefault и 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()
}
И да, у нас есть FormControl, называемый control, потому что у нас естьinput
<input style="text-transform: uppercase" [formControl]="control" placeholder="Qq yyyy" >
control:FormControl= new FormControl()
Чтобы контролировать ввод квартала и года вручную, мы подписываемся на control.valueChanges, чтобы присвоить значение году и кварталу, только если длина строки больше или равна 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: создать пользовательский элемент управления формы с компонентом, пересмотреть .css для улучшения и удаления ненужных стилей
Обновление преобразовать в CustomFormControl это просто, только реализует 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});
}
}
Я использую вспомогательную функцию
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);
}
}
Этот вызов в control.valueChanges подписывается и в функции щелчка
Я оставляю в этот стекблиц
ПРИМЕЧАНИЕ: подобно ng-bootstrap я выбираю, чтобы возвращаемое значение было объектом со свойствами год и квартал