radialapps/xunk-calendar

View on GitHub
src/xunk-calendar/xunk-calendar.component.ts

Summary

Maintainability
A
1 hr
Test Coverage
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

export interface XunkDate {
  date: number;
  month: any;
  year: any;
}

@Component({
    selector: 'xunk-calendar',
    templateUrl: './xunk-calendar.component.html',
    styleUrls: ['./xunk-calendar.component.css']
  })
  export class XunkCalendarComponent implements OnInit {

    /** Today */
    @Input() public today: XunkDate;

    /** The page open with [xx, month, year] */
    @Input() public openPage: XunkDate;

    /** Currently selected date */
    @Input() public selectedDate: XunkDate;

    /** Array with all the calendar data */
    @Input() public calendar: any[][] = [[]];

    /** Color for heat map */
    @Input() public heatMapColor = '#00ff00';

    /** Color for primary */
    @Input() public primaryColor = '#ff0000';

    /** Color for primary foreground */
    @Input() public primaryForeground = 'white';

    /** Heatmap data */
    @Input() public heatmap = {};

    /** Set this to false to hide month changing header */
    @Input() public showHeader = true;

    /** Emits the new date on change */
    @Output() change: EventEmitter<XunkDate> = new EventEmitter();

    /** Emits the new month with date as 1 on change */
    @Output() monthChange: EventEmitter<XunkDate> = new EventEmitter();

    private RGB_HM: any;
    private RGB_Primary: any;
    private RGB_Primary_FG: any;

    /** Constants */
    public readonly monthNames =
      [
        'January', 'February', 'March', 'April',
        'May', 'June', 'July', 'August',
        'September', 'October', 'November', 'December'
      ];
    public readonly dayNames =
      [
        'Sunday', 'Monday', 'Tuesday', 'Wednesday',
        'Thrusday', 'Friday', 'Saturday'
      ];

    /* Get RGB from CSS color */
    public static parseColor(input) {
      const div = document.createElement('div');
      div.style.color = input;
      document.body.appendChild(div);
      const m = getComputedStyle(div).color.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
      document.body.removeChild(div);

      if (m) {
        return {R: m[1], G: m[2], B: m[3]};
      } else {
        throw new Error('Color ' + input + ' could not be parsed.');
      }
    }

    /* Get today's date */
    public static getToday(): XunkDate {
      const dateNow = new Date();
      return {
        date: dateNow.getDate(),
        month: dateNow.getMonth(),
        year: dateNow.getFullYear()
      };
    }

    /** Pad number with zeros */
    public static zeropad(num, padlen, padchar = '0'): string {
      const pad_char = typeof padchar !== 'undefined' ? padchar : '0';
      const pad = new Array(1 + padlen).join(pad_char);
      return (pad + num).slice(-pad.length);
    }

    /** CalendarComponent */
    constructor() {
      /* Initialize */
      this.calendar = [];

      this.today = XunkCalendarComponent.getToday();
      this.openPage = {...this.today};
      this.selectedDate = {...this.today};
    }

    ngOnInit() {
      /* Parse colors*/
      this.RGB_HM = XunkCalendarComponent.parseColor(this.heatMapColor);
      this.RGB_Primary = XunkCalendarComponent.parseColor(this.primaryColor);
      this.RGB_Primary_FG = XunkCalendarComponent.parseColor(this.primaryForeground);

      /* Display initial */
      this.displayCalendar();
    }

    /**
     * Returns true if two dates are the same
     * with the date taken separately
     */
    protected sameDate(date: number, a: XunkDate, b: XunkDate): boolean {
      return date === b.date &&
             a.month === b.month &&
             a.year === b.year;
    }

    /** Returns true if fab! */
    protected isFab(col: number): string {
      /* Check if date is selected */
      if (this.sameDate(col, this.openPage, this.selectedDate)) {
        return 'primary';
      }

      /* No matches found */
      return '';
    }

    /** Returns 'primary' if col is today */
    public isToday(col: number): string {
      if (this.sameDate(col, this.openPage, this.today)) {
        return 'primary';
      }
      return '';
    }

    /** Select a day in the open page */
    public selectDay(col: number) {
      this.selectedDate.date = col;
      this.selectedDate.month = this.openPage.month;
      this.selectedDate.year = this.openPage.year;
      this.change.emit(this.selectedDate);
    }

    /** Change the month +1 or -1 */
    public changeMonth(diff: number) {
      this.openPage.month += diff;

      /* See if the year switches */
      if (this.openPage.month >= 12 ) {
        this.openPage.month = 0;
        this.openPage.year++;
      }

      if (this.openPage.month < 0 ) {
        this.openPage.month = 11;
        this.openPage.year--;
      }

      /* Refresh */
      this.displayCalendar();

      /* Emit event */
      this.monthChange.emit(this.openPage);
    }

    /** Compute the calendar */
    public displayCalendar() {
      /* Generate a new object */
      const newCalendar = [[]];

      const month = this.openPage.month;
      const year = this.openPage.year;

      /* Days in next month, and day of week */
      let col = new Date(year, month, 0).getDay();
      let row = 0, counter = 1;
      const numOfDays = Number(this.getDaysOfMonth(month, year));

      /* Loop to build the calendar body */
      while (counter <= numOfDays) {
         /* When to start new line */
         if (col > 6) {
             col = 0;
             newCalendar[++row] = [];
         }

         /* Set the value and increment */
         newCalendar[row][col++] = counter++;
      }

      /* Set the calendar to the newly computed one */
      this.calendar = newCalendar;
    }

    /** Gets the DaysPerMonth array */
    protected getDaysOfMonth(month: number, year: number): number {
      /* Check leap years if February */
      if (month === 1 && this.leapYear(year)) {
        return 29;
      }

      /** Return the number of days */
      return [31, 28, 31, 30, 31, 30,
        31, 31, 30, 31, 30, 31][month];
    }

    /** Returns true if leap year */
    protected leapYear(year: number): boolean {
      return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
    }

    /** Gets the heat map color */
    public getHM(day: number): string {
      /* If today */
      if (this.isFab(day)) {
        return `rgb(${this.RGB_Primary.R}, ${this.RGB_Primary.G}, ${this.RGB_Primary.B})`;
      }

      /* Return heatmap color */
      const zeropad = XunkCalendarComponent.zeropad;
      const ind = (zeropad(this.openPage.year, 4) + zeropad(this.openPage.month + 1, 2) + zeropad(day, 2));
      if (ind in this.heatmap) {
        return `rgba(${this.RGB_HM.R}, ${this.RGB_HM.G}, ${this.RGB_HM.B}, ${this.heatmap[ind]})`;
      } else {
        return 'inherit';
      }
    }

    public getForeground(day: number): string {
      /* If today */
      if (this.isFab(day)) {
        return `rgb(${this.RGB_Primary_FG.R}, ${this.RGB_Primary_FG.G}, ${this.RGB_Primary_FG.B})`;
      }
      if (this.isToday(day)) {
        return `rgb(${this.RGB_Primary.R}, ${this.RGB_Primary.G}, ${this.RGB_Primary.B})`;
      }
      return;
    }
}