季度日期选择器

Quarterly datepicker

我正在寻找使用 ng-bootstrap 的季度日期选择器。

目前我有月份和年份,请参阅 STACKBLITZ,但我想将月份更改为季度。

这可以用 ng-bootstrap 实现吗?

For info: here is a previous stackblitz example using Angular Material

“简要”解释一下 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>

此外,我们还展示了一个“header”,它有两个按钮(左箭头和右箭头)和一个显示年或十年的跨度

<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”做出一个或另一个动作

函数比较简单,再次考虑到一开始year和quarter是没有值的,那我们就用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()
  }

是的,我们有一个名为 control 的 FormControl,因为我们有一个输入

  <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 以改进和删除不必要的样式

Update 在 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 订阅和点击功能中调用

我在this stackblitz

离开

注意:像 ng-bootstrap 我选择 return 值是 object 属性年和季度

Update @Mohan 询问如何改进月份日期选择器,添加一个 minQuarter。

要添加 minQuarter 和 maxQuarter,它“仅”再添加两个输入

@Input() minQuarter={ quarter: "Q1", year: 0 };
@Input() maxQuarter={ quarter: "Q4", year: 9999 }

好吧,现在我们需要禁用按钮来考虑这个值。有几个按钮,禁用时需要小心

//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)"

此外,我们可以在手动更改季度时考虑,强制获取 minQuarter 或 maxQuarter。于是在订阅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;
    }
    ....
  });

}

如果我没有错过,就完了。我更新了 stackblitz