File

projects/common/lib/components/datepicker/datepicker.component.ts

Description

PRIME datepicker component. Largely a wrapper for ngx-mydatepicker https://github.com/kekeh/ngx-mydatepicker

NOTE - YOU MUST INCLUDE NGX-MYDATEPICKER IN YOUR PARENT APPLICATION TO USE THIS COMPONENT! This is due to some poor implementation in ngx-mydatepicker. Make sure to use the same version that this library uses.

Extends

Base

Implements

OnInit OnChanges ControlValueAccessor

Metadata

providers { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: forwardRef(() => DatepickerComponent) }
selector common-datepicker
styleUrls ./datepicker.component.scss
templateUrl ./datepicker.component.html
viewProviders { provide: ControlContainer, useExisting: forwardRef(() => NgForm) }

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor()

Inputs

clearButton
Type : "visible" | "invisible" | "none"
Default value : 'visible'

Control visibility of the clear 'x' button on the mini datepicker.

'visible' is default, button exists

'none' means the element does not exist

'invisible' means the element takes up space but is not visible / cannot be used.

Invisible is useful when you want to make sure a datepicker is the same size as a visible one.

date
Type : Date
dateFormat
Type : string
Default value : 'yyyy/mm/dd'

Format for how to display the date to the user.

disabled
Type : boolean
disableSince
Type : Date

Dates after disableSince will not be valid selections. Maps to a ngx-mydatepicker option, but we convert IMyDate to Date

disableUntil
Type : Date

Dates before disableUntil will not be valid selections. Maps to a ngx-mydatepicker option, but we convert IMyDate to Date

errorMessages
Type : DateErrorMsg
labelText
Type : string
onlyFutureDates
Type : boolean

Equivalent to setting disableBefore to tomorrow.

required
Type : boolean
Default value : false
size
Type : DatepickerSizes
Default value : DatepickerSizes.DEFAULT

Component size can be reduced, see Datepickersizes for options

Outputs

dateChange
Type : EventEmitter

Methods

clearDate
clearDate()
Returns : void
convertDateToSimpleDate
convertDateToSimpleDate(date: Date)
Parameters :
Name Type Optional
date Date No
Returns : IMyDate
convertSimpleDateToDate
convertSimpleDateToDate(date: IMyDate)
Parameters :
Name Type Optional
date IMyDate No
Returns : Date
isDate
isDate(x: any)
Parameters :
Name Type Optional
x any No
Returns : Date
ngOnChanges
ngOnChanges(changes: SimpleChanges)
Parameters :
Name Type Optional
changes SimpleChanges No
Returns : void
ngOnInit
ngOnInit()
Returns : void
onDateChanged
onDateChanged(event)
Parameters :
Name Optional
event No
Returns : void
registerOnChange
registerOnChange(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
registerOnTouched
registerOnTouched(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
writeValue
writeValue(obj: any)
Parameters :
Name Type Optional
obj any No
Returns : void

Properties

Public _onChange
Default value : () => {...}
Public _onTouched
Default value : () => {...}
datepickerOptions
Type : INgxMyDpOptions

Default options for wrapped ngx-datepicker.

DatepickerSizes
Default value : DatepickerSizes
model
Type : any

Datetime model used to interface with ngx-datepicker.

ngxdp
Type : NgxMyDatePickerDirective
Decorators :
@ViewChild('dp')
Public objectId
Type : string
Default value : UUID.UUID()
Inherited from Base
Defined in Base:11

An identifier for parents to keep track of components

Design Guidelines

Lorem ipsum dolor sit amet consectetur adipisicing elit. Sapiente, magnam ipsam. Sit quasi natus architecto rerum unde non provident! Quia nisi facere amet iste mollitia voluptatem non molestias esse optio?

Aperiam fugiat consectetur temporibus, iste repellat, quisquam sapiente nisi distinctio optio, autem nemo tenetur error eum voluptatibus ab accusamus quis voluptatum blanditiis. Quam et ut reprehenderit vitae nobis, at ipsum!

Exercitationem pariatur animi repudiandae corporis obcaecati ratione ducimus beatae quam, nostrum magnam unde numquam quidem cupiditate odit id. Beatae alias molestiae, optio incidunt harum quia voluptates deserunt sequi. Nesciunt, optio.

import { Component, OnInit, Input, Output, EventEmitter, SimpleChanges, ViewChild, OnChanges, forwardRef } from '@angular/core';
import { INgxMyDpOptions, IMyDate, NgxMyDatePickerDirective } from 'ngx-mydatepicker';
import { NgForm, ControlContainer, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
import { Base } from '../../models/base';

/**
 * Component NPM package dependencies:
 * a) moment
 *
 * This component reports the following errors.
 *    required
 *    dayOutOfRange
 *    yearDistantPast
 *    yearDistantFuture
 *    noFutureDatesAllowed
 *    invalidValue
 *
 *  These messages can be changed by updated messages using the errorMessages interface/
 *  Ex. { required: 'This field is required', invalidValue: '{label} is invalid' }
 */

export interface DateErrorMsg { // TODO: Remove - possible breaking change - currently datepicker uses it
  required?: string;
  dayOutOfRange?: string;
  yearDistantPast?: string;
  yearDistantFuture?: string;
  noPastDatesAllowed?: string;
  noFutureDatesAllowed?: string;
  invalidValue?: string;
}


/**
 * PRIME datepicker component. Largely a wrapper for ngx-mydatepicker
 * https://github.com/kekeh/ngx-mydatepicker
 *
 * NOTE - YOU MUST INCLUDE NGX-MYDATEPICKER IN YOUR PARENT APPLICATION TO USE
 * THIS COMPONENT!  This is due to some poor implementation in ngx-mydatepicker.
 * Make sure to use the same version that this library uses.
 */
@Component({
  selector: 'common-datepicker',
  templateUrl: './datepicker.component.html',
  styleUrls: ['./datepicker.component.scss'],
  /* Re-use the same ngForm that it's parent is using. The component will show
   * up in its parents `this.form`, and will auto-update `this.form.valid`
   */
  viewProviders: [ { provide: ControlContainer, useExisting: forwardRef(() => NgForm ) } ],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: forwardRef(() => DatepickerComponent)
    }
  ]
})
export class DatepickerComponent extends Base implements OnInit, OnChanges, ControlValueAccessor {
  /** Component size can be reduced, see Datepickersizes for options */
  @Input() size: DatepickerSizes = DatepickerSizes.DEFAULT;
  @Input() date: Date;
  @Output() dateChange = new EventEmitter<Date>();
  @Input() disabled: boolean;
  @Input() labelText: string;

  @Input() required: boolean = false;


  /** Dates **before** disableUntil will not be valid selections.  Maps to a ngx-mydatepicker option, but we convert IMyDate to Date  */
  @Input() disableUntil: Date;

  /** Dates **after** disableSince will not be valid selections.  Maps to a ngx-mydatepicker option, but we convert IMyDate to Date */
  @Input() disableSince: Date;

  /** Equivalent to setting disableBefore to tomorrow. */
  @Input() onlyFutureDates: boolean;

  /**
   * Control visibility of the clear 'x' button on the mini datepicker.
   *
   * **'visible'** is default, button exists
   *
   * **'none'** means the element does not exist
   *
   * **'invisible'** means the element takes up space but is not visible / cannot be
   * used.
   *
   * Invisible is useful when you want to make sure a datepicker is the same
   * size as a visible one.
   */
  @Input() clearButton: 'visible' | 'invisible' | 'none' = 'visible';



  /** Format for how to display the date to the user. */
  @Input() dateFormat: string = 'yyyy/mm/dd';

  @Input() errorMessages: DateErrorMsg;

  /** Datetime model used to interface with ngx-datepicker. */
  // model: any;
  model: any;

  // Make enum accessible in HTML
  DatepickerSizes: typeof DatepickerSizes = DatepickerSizes;

  @ViewChild('dp') ngxdp: NgxMyDatePickerDirective;

  /** Default options for wrapped ngx-datepicker. */
  datepickerOptions: INgxMyDpOptions;

  public _onChange = (_: any) => {};
  public _onTouched = () => {};

  constructor() {
    super();
   }

  convertDateToSimpleDate(date: Date): IMyDate {
    if (date === null || date === undefined) { return null; }
    return {
      year: date.getFullYear(),
      month: date.getMonth() + 1,
      day: date.getDate(),
    };
  }

  convertSimpleDateToDate(date: IMyDate): Date {
    // When ngx-mydatepicker is cleared, it returns {year: 0, month: 0, day: 0}
    if (date.year === 0) {
      return null;
    }
    return new Date(date.year, date.month - 1, date.day);
  }

  isDate(x: any): x is Date {
    if (!x) { return false; }
    return x.getDate !== undefined;
  }

  ngOnInit() {
    if (!this.errorMessages) {
      this.errorMessages = {
        required: this.labelText + ' is required.',
        dayOutOfRange: 'Invalid ' + this.labelText + '.',
        yearDistantPast: 'Invalid ' + this.labelText + '.',
        yearDistantFuture: 'Invalid ' + this.labelText + '.',
        noFutureDatesAllowed: 'Invalid ' + this.labelText + '.',
        invalidValue: 'Invalid ' + this.labelText + '.'
      };
    }

    this.datepickerOptions = {
      dateFormat: this.dateFormat,
      sunHighlight: false,
      appendSelectorToBody: true,
    };

    if (this.size === DatepickerSizes.MINI) {
      // Set width/height to 4/5 of default
      this.datepickerOptions.selectorHeight = '185px';
      this.datepickerOptions.selectorWidth = '201px';
    }

    if (this.isDate(this.disableSince)) {
      this.datepickerOptions.disableSince = this.convertDateToSimpleDate(this.disableSince);
    }

    if (this.isDate(this.disableUntil)) {
      this.datepickerOptions.disableUntil = this.convertDateToSimpleDate(this.disableSince);
    }

    if (this.onlyFutureDates) {
      const today = new Date();
      this.datepickerOptions.disableUntil = this.convertDateToSimpleDate(today);
    }


    if (this.date) {
      // Even if jsdate winds up being undefined, even defining this.model will
      // set the input as non-empty and it'll satisfy the 'required' validation.
      // So, we only add the model if there's actual data.
      this.model = {
        jsdate: this.date
      };
    }


  }


  ngOnChanges(changes: SimpleChanges) {
    // Parent component has passed in null, so we have to manually clear the input. This leads to 2 change detection cycles.
    // We could refactor it down to one, but the performance hit is minimal for such a simple component.
    if (this.date === null) {
      this.clearDate();
      this._onChange(null);
      this._onTouched();
    }
  }

  onDateChanged(event): void {
    if (event.jsdate || event.jsdate === null) {
      // Always emit a Date (or null)
      this.dateChange.emit(event.jsdate);
      this._onChange(event.jsdate);
      this._onTouched();
    }
  }

  clearDate() {
    if (this.ngxdp) {
      // We don't need to emit here, because by changing date we'll trigger onDateChanged automatically.
      this.date = null;
      this.ngxdp.clearDate();
    }
  }

  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this._onTouched = fn;
  }

  writeValue(obj: any): void {
    this.model = {
      jsdate: obj
    };
  }
}

export enum DatepickerSizes {
  MINI = 'mini',
  DEFAULT = 'default'
}
<div>
    <label><span>{{labelText}}</span>
        <div class="input-group">

                <input class="form-control" placeholder="YYYY/MM/DD"
                ngx-mydatepicker name="datepicker-{{labelText}}-{{objectId}}"
                [(ngModel)]="model"
                [options]="datepickerOptions"
                #dp="ngx-mydatepicker"
                #dateEl='ngModel'
                (dateChanged)="onDateChanged($event)"
                [required]='required'
                [disabled]="disabled"
                autocomplete="off"/>

            <span class="input-group-append float-label-append">
                <button type="button" class="btn btn-default" (click)="dp.toggleCalendar()">
                    <i class="fa fa-calendar"></i>
                </button>
            </span>
        </div>
    </label>
</div>

<!-- InvalidDateFormat comes from ngxmydatepicker, so we have a little less control over it. -->
<div *ngIf="!disabled && dateEl.touched"
    role="alert"
    class='error-container'
    aria-live="assertive">
  <div class="text-danger"
       *ngIf="(dateEl?.errors?.required) && errorMessages?.required && !dateEl?.errors?.invalidDateFormat">
       {{errorMessages.required}}
  </div>
  <!-- ngx-mydatepicker has it's own errors and we can't easily add our own, so we have reduced error messages. -->
  <!-- <div class="text-danger"
      *ngIf="dateEl?.errors?.dayOutOfRange && errorMessages?.dayOutOfRange">
       {{errorMessages.dayOutOfRange}}
  </div>
  <div class="text-danger"
       *ngIf="dateEl?.errors?.yearDistantPast && errorMessages?.yearDistantPast">
       {{errorMessages.yearDistantPast}}
  </div>
  <div class="text-danger"
       *ngIf="dateEl?.errors?.yearDistantFuture && errorMessages?.yearDistantFuture">
       {{errorMessages.yearDistantFuture}}
  </div>
  <div class="text-danger"
        *ngIf="dateEl?.errors?.noPastDatesAllowed && errorMessages?.noPastDatesAllowed">
        {{errorMessages.noPastDatesAllowed}}
  </div>

  <div class="text-danger"
       *ngIf="dateEl?.errors?.noFutureDatesAllowed && errorMessages?.noFutureDatesAllowed">
       {{errorMessages.noFutureDatesAllowed}}
  </div> -->
  <div class="text-danger"
       *ngIf="(dateEl?.errors?.invalidValue || dateEl?.errors?.invalidDateFormat) && errorMessages?.invalidValue">
       {{errorMessages.invalidValue}}
  </div>
</div>
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""