celsomarques/ionic-datepicker

View on GitHub
src/components/datepicker.component.ts

Summary

Maintainability
D
1 day
Test Coverage
import {Component, EventEmitter, ViewEncapsulation} from '@angular/core';
import {NavParams, ViewController} from 'ionic-angular';

import {DateService} from './datepicker.service';
import {FormControl} from '@angular/forms';

@Component({
    template: `
        <div class="datepicker-wrapper">
            <div class="datepicker-header"
                 [ngClass]="config.headerClasses">
                <div class="weekday-header">
                    <div class="weekday-title">{{getSelectedWeekday()}}</div>
                </div>
                <div class="date-header">
                    <div class="row">
                        <div class="col datepicker-month">
                            {{limitTo(getSelectedMonth(), 3)}}
                        </div>
                    </div>
                    <div class="row">
                        <div class="col datepicker-day-of-month ">
                            {{selectedDate | date: 'd'}}
                        </div>
                    </div>
                    <div class="row">
                        <div class="col datepicker-year ">
                            {{selectedDate | date: 'yyyy'}}
                        </div>
                    </div>
                </div>
            </div>
            <div class="datepicker-calendar"
                 [ngClass]="config.bodyClasses">
                <div class="row col datepicker-controls">
                    <button (click)="prevMonth()" [disabled]="previousDisabled"
                            ion-button=""
                            class="disable-hover button button-ios button-default button-default-ios">
                <span class="button-inner">
                    <ion-icon name="arrow-back" role="img"
                              class="icon icon-ios ion-ios-arrow-back" aria-label="arrow-back" ng-reflect-name="arrow-back">

                    </ion-icon></span>
                        <div class="button-effect"></div>
                    </button>
                    <select title="Month" name="equiptype" class="form-control" [formControl]="monthChanged" [(ngModel)]="selectedMonth" required>
                        <option></option>
                        <option *ngFor="let mon of months" [ngValue]="mon">{{mon}}</option>
                    </select>
                    <select title="Month" name="equiptype" class="form-control" [formControl]="yearChanged" [(ngModel)]="selectedYear" required>
                        <option></option>
                        <option *ngFor="let yea of yearsMaxMin" [ngValue]="yea">{{yea}}</option>
                    </select>
                    <button (click)="nextMonth()" [disabled]="nextDisabled"
                            ion-button=""
                            class="disable-hover button button-ios button-default button-default-ios">
                <span class="button-inner">
                    <ion-icon name="arrow-forward" role="img"
                              class="icon icon-ios ion-ios-arrow-forward" aria-label="arrow-forward" ng-reflect-name="arrow-forward">
                    </ion-icon></span>
                        <div class="button-effect"></div>
                    </button>
                </div>
                <div class="weekdays-row row">
            <span class="col calendar-cell"
                  *ngFor="let dayOfWeek of weekdays">
                    {{limitTo(dayOfWeek, 3)}}
                </span>
                </div>
                <div class="calendar-wrapper">
                    <div class="row calendar-row"
                         *ngFor="let week of rows;let i = index;">
                <span class="col calendar-cell"
                      *ngFor="let day of cols;let j=index;"
                      [ngClass]="{
                  'datepicker-date-col': getDate(i, j) !== undefined,
                  'datepicker-selected': isSelectedDate(getDate(i, j)),
                  'datepicker-current' : isActualDate(getDate(i, j)),
                  'datepicker-disabled': isDisabled(getDate(i, j))
                  }"
                      (click)="selectDate(getDate(i, j))">
                    {{getDate(i, j) | date:'d'}}
                </span>
                    </div>
                </div>
            </div>
            <div class="datepicker-footer">
                <button (click)="onCancel($event)"
                        ion-button=""
                        class="button button-clear button-small col-offset-33 disable-hover button button-ios button-default button-default-ios">
                    <span class="button-inner">{{config.cancelText || 'Cancel'}}</span>
                    <div class="button-effect"></div>
                </button>
                <button (click)="onDone($event)"
                        ion-button=""
                        class="button button-clear button-small disable-hover button button-ios button-default button-default-ios">
                    <span class="button-inner">{{config.okText || 'OK'}}</span>
                    <div class="button-effect"></div>
                </button>
            </div>
        </div>
    `,
    styles: [`
        ionic2-datepicker .datepicker-wrapper {
            height: 100%;
            background-color: white;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
        }

        ionic2-datepicker .datepicker-wrapper .datepicker-header {
            color: white;
            background-color: #009688;
            display: flex;
            flex-flow: column;
            height: 35%;
        }

        ionic2-datepicker .datepicker-wrapper .datepicker-header .date-header {
            display: flex;
            flex-flow: column;
            text-align: center;
        }

        ionic2-datepicker .datepicker-wrapper .datepicker-header .date-header .datepicker-day-of-month {
            font-size: 60px;
            font-weight: 700;
        }

        ionic2-datepicker .datepicker-wrapper .datepicker-header .date-header .datepicker-year, ionic2-datepicker .datepicker-wrapper .datepicker-header .date-header .datepicker-month {
            font-size: 14px;
            margin-top: 10px;
            margin-bottom: 10px;
        }

        ionic2-datepicker .datepicker-wrapper .datepicker-header .weekday-header {
            padding: 8px 10px;
            background-color: #008d7f;
        }

        ionic2-datepicker .datepicker-wrapper .datepicker-header .weekday-header .weekday-title {
            font-weight: bold;
            text-align: center;
        }

        ionic2-datepicker .weekdays-row {
            text-align: center;
        }

        ionic2-datepicker .datepicker-calendar {
            height: calc(100% - (35% + 60px));
        }

        ionic2-datepicker .datepicker-calendar .datepicker-controls {
            align-items: center;
            justify-content: space-between;
        }

        ionic2-datepicker .datepicker-calendar .calendar-wrapper {
            height: calc(100% - 60px - 40px);
            display: flex;
            flex-direction: column;
            justify-content: space-around;
        }

        ionic2-datepicker .datepicker-calendar .calendar-wrapper .datepicker-mark {
            background-color: #5b6c6b;
            border-radius: 20px;
        }

        ionic2-datepicker .datepicker-calendar .calendar-wrapper .datepicker-selected {
            background-color: #b6d9d6;
            border-radius: 20px;
        }

        ionic2-datepicker .datepicker-calendar .calendar-wrapper .datepicker-current {
            color: #3caa9f;
            border-radius: 20px;
        }

        ionic2-datepicker .datepicker-calendar .calendar-wrapper .datepicker-disabled {
            color: #aaaaaa;
        }

        ionic2-datepicker .datepicker-calendar .calendar-wrapper .calendar-cell {
            flex-flow: row wrap;
            text-align: center;
        }

        ionic2-datepicker .datepicker-footer {
            display: flex;
            justify-content: space-between;
            height: 60px;
        }

        ionic2-datepicker .datepicker-footer button {
            width: 100%;
        }

    `],
    selector: 'ionic2-datepicker',
    encapsulation: ViewEncapsulation.None,
})

export class DatePickerComponent {
    public config: {
        okText: string,
        cancelText: string,
        min: Date,
        max: Date,
        ionChanged: EventEmitter<Date>,
        ionSelected: EventEmitter<Date>,
        ionCanceled: EventEmitter<void>,
        headerClasses: string[],
        bodyClasses: string[],
        date: Date,
        disabledDates: Date[],
        markDates: Date[],
        showMaxAndMin: boolean;
    };
    public selectedDate: Date = new Date();
    public dateList: Date[];
    public cols: number[];
    public rows: number[];
    public weekdays: string[];
    public months: string[];
    public years: string[];
    public active: boolean = false;
    private tempDate: Date;
    private today: Date = new Date();


    public yearsMaxMin: number[];
    public selectedMonth;
    public selectedYear;
    public monthChanged: FormControl;
    public yearChanged: FormControl;
    public nextDisabled: boolean = false;
    public previousDisabled: boolean = false;
    private maxYear: number;
    private minYear: number;

    constructor(public viewCtrl: ViewController,
                public navParams: NavParams,
                public DatepickerService: DateService) {
        this.config = this.navParams.data;
        this.selectedDate = this.navParams.data.date;
        this.initialize();

    }


    /**
     * @function initialize - Initializes date variables
     */
    public initialize(): void {
        this.tempDate = this.selectedDate;
        this.createDateList(this.selectedDate);
        this.weekdays = this.DatepickerService.getDaysOfWeek();
        this.months = this.DatepickerService.getMonths();
        this.years = this.DatepickerService.getYears();
        this.initSelectBoxes();
        this.initSelectBoxListener();
    }

    /**
     * initializes the selectbox and considers max and min date
     */
    private initSelectBoxes() {
        let maxDate = this.config.max;
        let minDate = this.config.min;
        this.maxYear = Number.parseInt(this.years[this.years.length - 1]);
        this.minYear = Number.parseInt(this.years[0]);
        if (maxDate) {
            this.maxYear = maxDate.getFullYear();
        }
        if (minDate) {
            this.minYear = minDate.getFullYear();
        }
        this.yearsMaxMin = [];
        for (let _minYear = this.minYear; _minYear <= this.maxYear; _minYear++) {
            this.yearsMaxMin.push(_minYear);
        }
        this.selectedMonth = this.getSelectedMonth();
        this.selectedYear = this.getSelectedYear();
    }

    /**
     * initializes the listener to change the dategrid if that changes
     */
    private initSelectBoxListener() {
        this.monthChanged = new FormControl();
        this.monthChanged.valueChanges
            .subscribe(selectedMonth => {
                let monthAsNumber = this.months.indexOf(selectedMonth);
                let testDate: Date = new Date(this.tempDate.getTime());
                testDate.setMonth(monthAsNumber);
                this.tempDate = testDate;
                this.createDateList(this.tempDate);
                this.checkDisableButtons(monthAsNumber, testDate.getFullYear());
            });
        this.yearChanged = new FormControl();
        this.yearChanged.valueChanges
            .subscribe(selectedYear => {
                let testDate: Date = new Date(this.tempDate.getTime());
                testDate.setFullYear(selectedYear);
                this.tempDate = testDate;
                this.createDateList(this.tempDate);
                this.checkDisableButtons(testDate.getMonth(), selectedYear);
            });
    }

    /**
     * checks if the forward/previos buttons should be disabled or not
     * @param selectedMonth
     * @param selectedYear
     */
    checkDisableButtons(selectedMonth, selectedYear) {
        this.nextDisabled = selectedMonth == 11 && this.maxYear === selectedYear;
        this.previousDisabled = selectedMonth == 0 && this.minYear === selectedYear;
    }

    /**
     * @function createDateList - creates the list of dates
     * @param selectedDate - creates the list based on the currently selected date
     */
    public createDateList(selectedDate: Date): void {
        this.dateList = this.DatepickerService.createDateList(selectedDate);
        this.cols = new Array(7);
        this.rows = new Array(Math.ceil(this.dateList.length / this.cols.length));
    }

    /**
     * @function getDate - gets the actual date of date from the list of dates
     * @param row - the row of the date in a month. For instance 14 date would be 3rd or 2nd row
     * @param col - the column of the date in a month. For instance 1 would be on the column of the weekday.
     */
    public getDate(row: number, col: number): Date {
        /**
         * @description The locale en-US is noted for the sake of starting with monday if its in usa
         */
        return this.dateList[(row * 7 + col) + ((this.DatepickerService.doesStartFromMonday()) ? 1 : 0)];
    }

    /**
     * @function getDate - gets the actual number of date from the list of dates
     * @param row - the row of the date in a month. For instance 14 date would be 3rd or 2nd row
     * @param col - the column of the date in a month. For instance 1 would be on the column of the weekday.
     */
    public getDateAsDay(row: number, col: number): number {
        let date = this.getDate(row, col);
        if (date) return date.getDate();
    }


    /**
     * @function isDisabled - Checks whether the date should be disabled or not
     * @param date - the date to test against
     */
    public isDisabled(date: Date): boolean {
        if (!date) return true;
        if (this.config.min) {
            this.config.min.setHours(0, 0, 0, 0);
            if (date < this.config.min) return true;
        }
        if (this.config.max) {
            this.config.max.setHours(0, 0, 0, 0);
            if (date > this.config.max) return true;
        }
        if (this.config.disabledDates) {
            return this.config.disabledDates.some(disabledDate =>
                this.areEqualDates(new Date(disabledDate), date));
        }
        return false;
    }

    public isMark(date: Date): boolean {
        if (!date) return false;
        if (this.config.markDates) {
            return this.config.markDates.some(markDate =>
                this.areEqualDates(new Date(markDate), date));
        }
        return false
    }

    public isActualDate(date: Date): boolean {
        if (!date) return false;
        return this.areEqualDates(date, this.today);
    }

    public isActualMonth(month: number): boolean {
        return month === this.today.getMonth();
    }

    public isActualYear(year: number): boolean {
        return year === this.today.getFullYear();
    }

    public isSelectedDate(date: Date): boolean {
        if (!date) return false;
        return this.areEqualDates(date, this.selectedDate);
    }

    public isSelectedMonth(month: number): boolean {
        return month === this.tempDate.getMonth();
    }

    public isSelectedYear(year: number): boolean {
        return year === this.tempDate.getFullYear();
    }


    public selectDate(date: Date): void {
        if (this.isDisabled(date)) return;
        this.selectedDate = date;
        this.selectedDate.setHours(0, 0, 0, 0);
        this.tempDate = this.selectedDate;
        this.config.ionSelected.emit(this.tempDate);
    }


    public getSelectedWeekday(): string {
        return this.weekdays[this.selectedDate.getDay()];
    }

    public getSelectedMonth(date?: Date): string {
        if (!date) {
            return this.months[this.selectedDate.getMonth()];
        } else {
            return this.months[date.getMonth()];
        }
    }

    public getTempMonth() {
        return this.months[this.tempDate.getMonth()];
    }

    public getTempYear() {
        return (this.tempDate || this.selectedDate).getFullYear();
    }

    public getSelectedDate() {
        return (this.selectedDate || new Date()).getDate();
    }

    public getSelectedYear() {
        return (this.selectedDate || new Date()).getFullYear();
    }


    public onCancel(e: Event) {
        if (this.config.date)
            this.selectedDate = this.config.date || new Date();
        this.config.ionCanceled.emit();
        this.viewCtrl.dismiss();
    };

    public onDone(e: Event) {
        this.config.date = this.selectedDate;
        this.config.ionChanged.emit(this.config.date);
        this.viewCtrl.dismiss();
    };

    public selectMonthOrYear() {
        this.createDateList(this.tempDate);
        if (this.isDisabled(this.tempDate)) return;
        this.selectedDate = this.tempDate;
    }

    private areEqualDates(dateA: Date, dateB: Date) {
        return dateA.getDate() === dateB.getDate() &&
            dateA.getMonth() === dateB.getMonth() &&
            dateA.getFullYear() === dateB.getFullYear();
    }

    public limitTo(arr: Array<string> | string, limit: number): Array<string> | string {
        if (this.DatepickerService.locale === 'custom') return arr;
        if (this.DatepickerService.locale === 'de') limit = 2;
        if (Array.isArray(arr))
            return arr.splice(0, limit);
        if (this.DatepickerService.locale === 'zh-CN' || this.DatepickerService.locale === 'zh-TW')
            arr = arr.replace('星期', '')
        return (<string>arr).slice(0, limit);
    }

    public getMonthRows(): {}[] {
        return [];
    }


    public nextMonth() {
        //if (this.max.getMonth() < this.tempDate.getMonth() + 1 && this.min.getFullYear() === this.tempDate.getFullYear()) return;
        let testDate: Date = new Date(this.tempDate.getTime());
        testDate.setDate(1);

        if (testDate.getMonth() === 11) {
            testDate.setFullYear(testDate.getFullYear() + 1);
            testDate.setMonth(0);
        }
        else {
            testDate.setMonth(testDate.getMonth() + 1);
        }
        if ((!this.config.max || this.config.max >= testDate) || this.config.showMaxAndMin) {
            this.setDateAfterSelection(testDate);
        }
    }

    public prevMonth() {
        let testDate: Date = new Date(this.tempDate.getTime());
        testDate.setDate(0);
        // testDate.setDate(this.tempDate.getDate());
        if ((!this.config.min ||
                (this.config.min <= testDate)) || this.config.showMaxAndMin) {
            this.setDateAfterSelection(testDate);
        }
    }

    /**
     * calls to create the days (list/grid) if applicable
     * @param {Date} testDate
     */
    private setDateAfterSelection(testDate: Date) {
        this.tempDate = testDate;
        this.createDateList(this.tempDate);
        this.selectedMonth = this.getSelectedMonth(this.tempDate);
        this.selectedYear = this.tempDate.getFullYear();
        this.checkDisableButtons(this.tempDate.getMonth(), this.selectedYear);
    }
}