From b40f5b0ccc6cb86cae9557b3404f43fd5f3476d8 Mon Sep 17 00:00:00 2001 From: Blackbaud-PatrickOFriel Date: Tue, 11 Apr 2017 16:33:54 -0400 Subject: [PATCH 01/19] Start of datepicker calendar. Things to think about: - Instead of using Javascript Date, think about using skyDate class with day, month, year properties. - Get everything working with simplest case --- .../datepicker-calendar/date-formatter.ts | 7 + .../datepicker-calendar-inner.component.html | 6 + .../datepicker-calendar-inner.component.ts | 311 ++++++++++++++++++ .../datepicker-calendar.component.html | 28 ++ .../datepicker-calendar.component.ts | 102 ++++++ .../datepicker-calendar/datepicker.config.ts | 21 ++ .../daypicker.component.html | 59 ++++ .../daypicker.component.ts | 88 +++++ .../monthpicker.component.html | 38 +++ .../monthpicker.component.ts | 52 +++ .../yearpicker.component.html | 39 +++ .../yearpicker.component.ts | 56 ++++ 12 files changed, 807 insertions(+) create mode 100644 src/modules/datepicker-calendar/date-formatter.ts create mode 100644 src/modules/datepicker-calendar/datepicker-calendar-inner.component.html create mode 100644 src/modules/datepicker-calendar/datepicker-calendar-inner.component.ts create mode 100644 src/modules/datepicker-calendar/datepicker-calendar.component.html create mode 100644 src/modules/datepicker-calendar/datepicker-calendar.component.ts create mode 100644 src/modules/datepicker-calendar/datepicker.config.ts create mode 100644 src/modules/datepicker-calendar/daypicker.component.html create mode 100644 src/modules/datepicker-calendar/daypicker.component.ts create mode 100644 src/modules/datepicker-calendar/monthpicker.component.html create mode 100644 src/modules/datepicker-calendar/monthpicker.component.ts create mode 100644 src/modules/datepicker-calendar/yearpicker.component.html create mode 100644 src/modules/datepicker-calendar/yearpicker.component.ts diff --git a/src/modules/datepicker-calendar/date-formatter.ts b/src/modules/datepicker-calendar/date-formatter.ts new file mode 100644 index 000000000..3585cc725 --- /dev/null +++ b/src/modules/datepicker-calendar/date-formatter.ts @@ -0,0 +1,7 @@ +let moment = require('moment'); + +export class SkyDateFormatter { + public format(date:Date, format:string):string { + return moment(date.getTime()).format(format); + } +} diff --git a/src/modules/datepicker-calendar/datepicker-calendar-inner.component.html b/src/modules/datepicker-calendar/datepicker-calendar-inner.component.html new file mode 100644 index 000000000..2859873f5 --- /dev/null +++ b/src/modules/datepicker-calendar/datepicker-calendar-inner.component.html @@ -0,0 +1,6 @@ +
+ +
diff --git a/src/modules/datepicker-calendar/datepicker-calendar-inner.component.ts b/src/modules/datepicker-calendar/datepicker-calendar-inner.component.ts new file mode 100644 index 000000000..866ede6c6 --- /dev/null +++ b/src/modules/datepicker-calendar/datepicker-calendar-inner.component.ts @@ -0,0 +1,311 @@ +import { + Component, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges +} from '@angular/core'; + +import { SkyDateFormatter } from './date-formatter'; + +// const MIN_DATE:Date = void 0; +// const MAX_DATE:Date = void 0; +// const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +/* + const KEYS = { + 13: 'enter', + 32: 'space', + 33: 'pageup', + 34: 'pagedown', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down' + }; + */ + +@Component({ + selector: 'sky-datepicker-inner', + templateUrl: './datepicker-calendar-inner.component.html' +}) +export class SkyDatepickerCalendarInnerComponent implements OnInit, OnChanges { + @Input() + public datepickerMode: string; + @Input() + public startingDay: number; + @Input() + public yearRange: number; + + @Input() + public minDate: Date; + @Input() + public maxDate: Date; + @Input() + public minMode: string; + @Input() + public maxMode: string; + + @Input() + public monthColLimit: number; + @Input() + public yearColLimit: number; + + @Input() + public formatDay: string; + @Input() + public formatMonth: string; + @Input() + public formatYear: string; + + @Input() + public dateDisabled: { date: Date, mode: string }[]; + @Input() + public initDate: Date; + + @Output() + public activeDateChange: EventEmitter = new EventEmitter(undefined); + + public stepDay: any = {}; + public stepMonth: any = {}; + public stepYear: any = {}; + + protected modes: string[] = ['day', 'month', 'year']; + protected dateFormatter: SkyDateFormatter = new SkyDateFormatter(); + protected uniqueId: string; + protected _activeDate: Date; + protected selectedDate: Date; + protected activeDateId: string; + + protected refreshViewHandlerDay: Function; + protected compareHandlerDay: Function; + protected refreshViewHandlerMonth: Function; + protected compareHandlerMonth: Function; + protected refreshViewHandlerYear: Function; + protected compareHandlerYear: Function; + + @Input() + public get activeDate(): Date { + return this._activeDate; + } + + public set activeDate(value: Date) { + this._activeDate = value; + } + + // todo: add formatter value to Date object + public ngOnInit(): void { + // todo: use date for unique value + this.uniqueId = 'datepicker-' + '-' + Math.floor(Math.random() * 10000); + + if (this.initDate) { + this.activeDate = this.initDate; + this.selectedDate = new Date(this.activeDate.valueOf() as number); + this.activeDateChange.emit(this.activeDate); + } else if (this.activeDate === undefined) { + this.activeDate = new Date(); + } + } + + public ngOnChanges(): void { + this.refreshView(); + } + + public setCompareHandler(handler: Function, type: string): void { + if (type === 'day') { + this.compareHandlerDay = handler; + } + + if (type === 'month') { + this.compareHandlerMonth = handler; + } + + if (type === 'year') { + this.compareHandlerYear = handler; + } + } + + public compare(date1: Date, date2: Date): number | undefined { + if (date1 === undefined || date2 === undefined) { + return undefined; + } + + if (this.datepickerMode === 'day' && this.compareHandlerDay) { + return this.compareHandlerDay(date1, date2); + } + + if (this.datepickerMode === 'month' && this.compareHandlerMonth) { + return this.compareHandlerMonth(date1, date2); + } + + if (this.datepickerMode === 'year' && this.compareHandlerYear) { + return this.compareHandlerYear(date1, date2); + } + + return void 0; + } + + public setRefreshViewHandler(handler: Function, type: string): void { + if (type === 'day') { + this.refreshViewHandlerDay = handler; + } + + if (type === 'month') { + this.refreshViewHandlerMonth = handler; + } + + if (type === 'year') { + this.refreshViewHandlerYear = handler; + } + } + + public refreshView(): void { + if (this.datepickerMode === 'day' && this.refreshViewHandlerDay) { + this.refreshViewHandlerDay(); + } + + if (this.datepickerMode === 'month' && this.refreshViewHandlerMonth) { + this.refreshViewHandlerMonth(); + } + + if (this.datepickerMode === 'year' && this.refreshViewHandlerYear) { + this.refreshViewHandlerYear(); + } + } + + public dateFilter(date: Date, format: string): string { + return this.dateFormatter.format(date, format); + } + + public isActive(dateObject: any): boolean { + if (this.compare(dateObject.date, this.activeDate) === 0) { + this.activeDateId = dateObject.uid; + return true; + } + + return false; + } + + public createDateObject(date: Date, format: string): any { + let dateObject: any = {}; + dateObject.date = new Date(date.getFullYear(), date.getMonth(), date.getDate()); + dateObject.label = this.dateFilter(date, format); + dateObject.selected = this.compare(date, this.selectedDate) === 0; + dateObject.disabled = this.isDisabled(date); + dateObject.current = this.compare(date, new Date()) === 0; + return dateObject; + } + + public split(arr: any[], size: number): any[] { + let arrays: any[] = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + } + + // Fix a hard-reproducible bug with timezones + // The bug depends on OS, browser, current timezone and current date + // i.e. + // var date = new Date(2014, 0, 1); + // console.log(date.getFullYear(), date.getMonth(), date.getDate(), + // date.getHours()); can result in "2013 11 31 23" because of the bug. + public fixTimeZone(date: Date): Date { + let hours = date.getHours(); + return new Date(date.getFullYear(), date.getMonth(), date.getDate(), hours === 23 ? hours + 2 : 0); + } + + public select(date: Date): void { + if (this.datepickerMode === this.minMode) { + if (!this.activeDate) { + this.activeDate = new Date(0, 0, 0, 0, 0, 0, 0); + } + + this.activeDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); + + } else { + + this.activeDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); + this.datepickerMode = this.modes[this.modes.indexOf(this.datepickerMode) - 1]; + } + + this.selectedDate = new Date(this.activeDate.valueOf() as number); + this.activeDateChange.emit(this.activeDate); + this.refreshView(); + } + + public move(direction: number): void { + let expectedStep: any; + if (this.datepickerMode === 'day') { + expectedStep = this.stepDay; + } + + if (this.datepickerMode === 'month') { + expectedStep = this.stepMonth; + } + + if (this.datepickerMode === 'year') { + expectedStep = this.stepYear; + } + + if (expectedStep) { + let year = this.activeDate.getFullYear() + direction * (expectedStep.years || 0); + let month = this.activeDate.getMonth() + direction * (expectedStep.months || 0); + this.activeDate = new Date(year, month, 1); + + this.refreshView(); + this.activeDateChange.emit(this.activeDate); + } + } + + public toggleMode(direction: number): void { + direction = direction || 1; + + if ((this.datepickerMode === this.maxMode && direction === 1) || + (this.datepickerMode === this.minMode && direction === -1)) { + return; + } + + this.datepickerMode = this.modes[this.modes.indexOf(this.datepickerMode) + direction]; + this.refreshView(); + } + + + protected compareDateDisabled(date1Disabled: { date: Date, mode: string }, date2: Date): number { + if (date1Disabled === undefined || date2 === undefined) { + return undefined; + } + + if (date1Disabled.mode === 'day' && this.compareHandlerDay) { + return this.compareHandlerDay(date1Disabled.date, date2); + } + + if (date1Disabled.mode === 'month' && this.compareHandlerMonth) { + return this.compareHandlerMonth(date1Disabled.date, date2); + } + + if (date1Disabled.mode === 'year' && this.compareHandlerYear) { + return this.compareHandlerYear(date1Disabled.date, date2); + } + + return undefined; + } + + protected isDisabled(date: Date): boolean { + let isDateDisabled: boolean = false; + if (this.dateDisabled) { + this.dateDisabled.forEach((disabledDate: { date: Date, mode: string }) => { + if (this.compareDateDisabled(disabledDate, date) === 0) { + isDateDisabled = true; + } + }); + } + + return (isDateDisabled || (this.minDate && this.compare(date, this.minDate) < 0) || + (this.maxDate && this.compare(date, this.maxDate) > 0)); + } +} diff --git a/src/modules/datepicker-calendar/datepicker-calendar.component.html b/src/modules/datepicker-calendar/datepicker-calendar.component.html new file mode 100644 index 000000000..440598799 --- /dev/null +++ b/src/modules/datepicker-calendar/datepicker-calendar.component.html @@ -0,0 +1,28 @@ + + + + + diff --git a/src/modules/datepicker-calendar/datepicker-calendar.component.ts b/src/modules/datepicker-calendar/datepicker-calendar.component.ts new file mode 100644 index 000000000..a93fdc74d --- /dev/null +++ b/src/modules/datepicker-calendar/datepicker-calendar.component.ts @@ -0,0 +1,102 @@ +import { + Component, + EventEmitter, + Input, + Output, + ViewChild +} from '@angular/core'; +import { + SkyDatepickerCalendarInnerComponent +} from './datepicker-calendar-inner.component'; +import { + SkyDatepickerConfig +} from './datepicker.config'; + +/* tslint:disable:component-selector-name component-selector-type */ +@Component({ + selector: 'sky-datepicker', + templateUrl: './datepicker-calendar.component.html' +}) +export class SkyDatepickerComponent { + /** sets datepicker mode, supports: `day`, `month`, `year` */ + @Input() + public datepickerMode: string = 'day'; + /** default date to show if `ng-model` value is not specified */ + @Input() + public initDate: Date; + /** oldest selectable date */ + @Input() + public minDate: Date; + /** latest selectable date */ + @Input() + public maxDate: Date; + /** format of day in month */ + @Input() + public formatDay: string; + /** format of month in year */ + @Input() + public formatMonth: string; + /** format of year in year range */ + @Input() + public formatYear: string; + /** array of disabled dates */ + @Input() + public dateDisabled: { date: Date, mode: string }[]; + /** starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday) */ + @Input() + public startingDay: number; + + /** currently active date */ + @Input() + public get activeDate(): Date { + return this._activeDate || this._now; + } + + public set activeDate(value: Date) { + this._activeDate = value; + } + + @Output() + public selectionDone: EventEmitter = new EventEmitter(undefined); + + @Output() + public activeDateChange: EventEmitter = new EventEmitter(undefined); + + @ViewChild(SkyDatepickerCalendarInnerComponent) + public _datepicker: SkyDatepickerCalendarInnerComponent; + + public minMode: string; + public maxMode: string; + public monthColLimit: number; + public yearColLimit: number; + + protected _now: Date = new Date(); + protected _activeDate: Date; + protected config: SkyDatepickerConfig; + + public constructor(config: SkyDatepickerConfig) { + this.config = config; + this.configureOptions(); + } + + public configureOptions(): void { + Object.assign(this, this.config); + } + + public onActiveDateChange(event: Date): void { + this.activeDateChange.emit(event); + } + + public writeValue(value: any): void { + if (this._datepicker.compare(value, this._activeDate) === 0) { + return; + } + if (value && value instanceof Date) { + this.activeDate = value; + this._datepicker.select(value); + return; + } + + this.activeDate = value ? new Date(value) : void 0; + } +} diff --git a/src/modules/datepicker-calendar/datepicker.config.ts b/src/modules/datepicker-calendar/datepicker.config.ts new file mode 100644 index 000000000..c4b269236 --- /dev/null +++ b/src/modules/datepicker-calendar/datepicker.config.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class SkyDatepickerConfig { + public datepickerMode: string = 'day'; + public startingDay: number = 0; + public yearRange: number = 20; + public minMode: string = 'day'; + public maxMode: string = 'year'; + public showWeeks: boolean = true; + public formatDay: string = 'DD'; + public formatMonth: string = 'MMMM'; + public formatYear: string = 'YYYY'; + public formatDayHeader: string = 'dd'; + public formatDayTitle: string = 'MMMM YYYY'; + public formatMonthTitle: string = 'YYYY'; + public onlyCurrentMonth: boolean = false; + public monthColLimit: number = 3; + public yearColLimit: number = 5; + public shortcutPropagation: boolean = false; +} diff --git a/src/modules/datepicker-calendar/daypicker.component.html b/src/modules/datepicker-calendar/daypicker.component.html new file mode 100644 index 000000000..76e793c00 --- /dev/null +++ b/src/modules/datepicker-calendar/daypicker.component.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + +
+ + + + + +
+ {{labelz.abbr}} +
diff --git a/src/modules/datepicker-calendar/daypicker.component.ts b/src/modules/datepicker-calendar/daypicker.component.ts new file mode 100644 index 000000000..690bccb6c --- /dev/null +++ b/src/modules/datepicker-calendar/daypicker.component.ts @@ -0,0 +1,88 @@ +import { + Component, + OnInit +} from '@angular/core'; +import { + SkyDatepickerCalendarInnerComponent +} from './datepicker-calendar-inner.component'; + +@Component({ + selector: 'sky-daypicker', + templateUrl: 'daypicker.component.html' +}) +export class SkyDayPickerComponent implements OnInit { + + public labels: any[] = []; + public title: string; + public rows: any[] = []; + public weekNumbers: number[] = []; + public datepicker: SkyDatepickerCalendarInnerComponent; + public CURRENT_THEME_TEMPLATE: any; + + public constructor(datePicker: SkyDatepickerCalendarInnerComponent) { + this.datepicker = datePicker; + } + + public ngOnInit(): void { + let self = this; + + this.datepicker.stepDay = {months: 1}; + + this.datepicker.setRefreshViewHandler(function (): void { + let year = this.activeDate.getFullYear(); + let month = this.activeDate.getMonth(); + let firstDayOfMonth = new Date(year, month, 1); + let difference = this.startingDay - firstDayOfMonth.getDay(); + let numDisplayedFromPreviousMonth = (difference > 0) + ? 7 - difference + : -difference; + let firstDate = new Date(firstDayOfMonth.getTime()); + + if (numDisplayedFromPreviousMonth > 0) { + firstDate.setDate(-numDisplayedFromPreviousMonth + 1); + } + + // 42 is the number of days on a six-week calendar + let _days: Date[] = self.getDates(firstDate, 42); + let days: any[] = []; + for (let i = 0; i < 42; i++) { + let _dateObject = this.createDateObject(_days[i], this.formatDay); + _dateObject.secondary = _days[i].getMonth() !== month; + _dateObject.uid = this.uniqueId + '-' + i; + days[i] = _dateObject; + } + + self.labels = []; + for (let j = 0; j < 7; j++) { + self.labels[j] = {}; + self.labels[j].abbr = this.dateFilter(days[j].date, this.formatDayHeader); + self.labels[j].full = this.dateFilter(days[j].date, 'EEEE'); + } + + self.title = this.dateFilter(this.activeDate, this.formatDayTitle); + self.rows = this.split(days, 7); + }, 'day'); + + this.datepicker.setCompareHandler(function (date1: Date, date2: Date): number { + let d1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()); + let d2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); + return d1.getTime() - d2.getTime(); + }, 'day'); + + this.datepicker.refreshView(); + } + + protected getDates(startDate: Date, n: number): Date[] { + let dates: Date[] = new Array(n); + let current = new Date(startDate.getTime()); + let i = 0; + let date: Date; + while (i < n) { + date = new Date(current.getTime()); + date = this.datepicker.fixTimeZone(date); + dates[i++] = date; + current = new Date(current.getFullYear(), current.getMonth(), current.getDate() + 1); + } + return dates; + } +} diff --git a/src/modules/datepicker-calendar/monthpicker.component.html b/src/modules/datepicker-calendar/monthpicker.component.html new file mode 100644 index 000000000..3939c57c2 --- /dev/null +++ b/src/modules/datepicker-calendar/monthpicker.component.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + +
+ + + + +
+ +
diff --git a/src/modules/datepicker-calendar/monthpicker.component.ts b/src/modules/datepicker-calendar/monthpicker.component.ts new file mode 100644 index 000000000..0fe9f58f9 --- /dev/null +++ b/src/modules/datepicker-calendar/monthpicker.component.ts @@ -0,0 +1,52 @@ +import { + Component, + OnInit +} from '@angular/core'; +import { + SkyDatepickerCalendarInnerComponent +} from './datepicker-calendar-inner.component'; + +@Component({ + selector: 'sky-monthpicker', + templateUrl: 'monthpicker.component.html' +}) +export class SkyMonthPickerComponent implements OnInit { + public title:string; + public rows:any[] = []; + public datepicker: SkyDatepickerCalendarInnerComponent; + public maxMode:string; + + public constructor(datePicker: SkyDatepickerCalendarInnerComponent) { + this.datepicker = datePicker; + } + + public ngOnInit():void { + let self = this; + + this.datepicker.stepMonth = {years: 1}; + + this.datepicker.setRefreshViewHandler(function ():void { + let months:any[] = new Array(12); + let year:number = this.activeDate.getFullYear(); + let date:Date; + + for (let i = 0; i < 12; i++) { + date = new Date(year, i, 1); + date = this.fixTimeZone(date); + months[i] = this.createDateObject(date, this.formatMonth); + months[i].uid = this.uniqueId + '-' + i; + } + + self.title = this.dateFilter(this.activeDate, this.formatMonthTitle); + self.rows = this.split(months, self.datepicker.monthColLimit); + }, 'month'); + + this.datepicker.setCompareHandler(function (date1:Date, date2:Date):number { + let d1 = new Date(date1.getFullYear(), date1.getMonth()); + let d2 = new Date(date2.getFullYear(), date2.getMonth()); + return d1.getTime() - d2.getTime(); + }, 'month'); + + this.datepicker.refreshView(); + } +} diff --git a/src/modules/datepicker-calendar/yearpicker.component.html b/src/modules/datepicker-calendar/yearpicker.component.html new file mode 100644 index 000000000..32f491a2c --- /dev/null +++ b/src/modules/datepicker-calendar/yearpicker.component.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + +
+ + + + + +
+ +
diff --git a/src/modules/datepicker-calendar/yearpicker.component.ts b/src/modules/datepicker-calendar/yearpicker.component.ts new file mode 100644 index 000000000..09cde96ca --- /dev/null +++ b/src/modules/datepicker-calendar/yearpicker.component.ts @@ -0,0 +1,56 @@ +import { + Component, + OnInit +} from '@angular/core'; + +import { + SkyDatepickerCalendarInnerComponent +} from './datepicker-calendar-inner.component'; + +@Component({ + selector: 'sky-yearpicker', + templateUrl: 'yearpicker.component.html' +}) +export class SkyYearPickerComponent implements OnInit { + public datepicker: SkyDatepickerCalendarInnerComponent; + public title:string; + public rows:any[] = []; + + public constructor(datePicker: SkyDatepickerCalendarInnerComponent) { + this.datepicker = datePicker; + } + + public ngOnInit():void { + let self = this; + + this.datepicker.stepYear = {years: this.datepicker.yearRange}; + + this.datepicker.setRefreshViewHandler(function ():void { + let years:any[] = new Array(this.yearRange); + let date:Date; + let start = self.getStartingYear(this.activeDate.getFullYear()); + + for (let i = 0; i < this.yearRange; i++) { + date = new Date(start + i, 0, 1); + date = this.fixTimeZone(date); + years[i] = this.createDateObject(date, this.formatYear); + years[i].uid = this.uniqueId + '-' + i; + } + + self.title = [years[0].label, + years[this.yearRange - 1].label].join(' - '); + self.rows = this.split(years, self.datepicker.yearColLimit); + }, 'year'); + + this.datepicker.setCompareHandler(function (date1:Date, date2:Date):number { + return date1.getFullYear() - date2.getFullYear(); + }, 'year'); + + this.datepicker.refreshView(); + } + + protected getStartingYear(year:number):number { + // todo: parseInt + return ((year - 1) / this.datepicker.yearRange) * this.datepicker.yearRange + 1; + } +} From 1e82d3635f5dd2a9fb37ad3fed4b8e60d5f42b42 Mon Sep 17 00:00:00 2001 From: Blackbaud-PatrickOFriel Date: Wed, 12 Apr 2017 15:47:42 -0400 Subject: [PATCH 02/19] Get working datepicker calendar --- .../datepicker/datepicker-demo.component.html | 5 + .../datepicker/datepicker-demo.component.ts | 9 + src/app/components/datepicker/index.html | 28 ++++ src/app/components/demo-components.service.ts | 21 +++ src/core.ts | 5 +- .../datepicker-calendar.component.html | 28 ---- .../datepicker-calendar.component.ts | 102 ----------- .../datepicker-calendar/datepicker.config.ts | 21 --- .../yearpicker.component.html | 39 ----- .../date-formatter.ts | 0 .../datepicker-calendar-inner.component.html | 0 .../datepicker-calendar-inner.component.ts | 158 +++++++----------- .../datepicker-calendar.component.html | 10 ++ .../datepicker-calendar.component.ts | 69 ++++++++ .../datepicker/datepicker-config.service.ts | 8 + src/modules/datepicker/datepicker.module.ts | 36 ++++ .../daypicker.component.html | 39 ++--- .../daypicker.component.ts | 4 +- src/modules/datepicker/index.ts | 8 + .../monthpicker.component.html | 24 +-- .../monthpicker.component.ts | 4 +- .../datepicker/yearpicker.component.html | 39 +++++ .../yearpicker.component.ts | 4 +- 23 files changed, 330 insertions(+), 331 deletions(-) create mode 100644 src/app/components/datepicker/datepicker-demo.component.html create mode 100644 src/app/components/datepicker/datepicker-demo.component.ts create mode 100644 src/app/components/datepicker/index.html delete mode 100644 src/modules/datepicker-calendar/datepicker-calendar.component.html delete mode 100644 src/modules/datepicker-calendar/datepicker-calendar.component.ts delete mode 100644 src/modules/datepicker-calendar/datepicker.config.ts delete mode 100644 src/modules/datepicker-calendar/yearpicker.component.html rename src/modules/{datepicker-calendar => datepicker}/date-formatter.ts (100%) rename src/modules/{datepicker-calendar => datepicker}/datepicker-calendar-inner.component.html (100%) rename src/modules/{datepicker-calendar => datepicker}/datepicker-calendar-inner.component.ts (58%) create mode 100644 src/modules/datepicker/datepicker-calendar.component.html create mode 100644 src/modules/datepicker/datepicker-calendar.component.ts create mode 100644 src/modules/datepicker/datepicker-config.service.ts create mode 100644 src/modules/datepicker/datepicker.module.ts rename src/modules/{datepicker-calendar => datepicker}/daypicker.component.html (52%) rename src/modules/{datepicker-calendar => datepicker}/daypicker.component.ts (96%) create mode 100644 src/modules/datepicker/index.ts rename src/modules/{datepicker-calendar => datepicker}/monthpicker.component.html (52%) rename src/modules/{datepicker-calendar => datepicker}/monthpicker.component.ts (93%) create mode 100644 src/modules/datepicker/yearpicker.component.html rename src/modules/{datepicker-calendar => datepicker}/yearpicker.component.ts (93%) diff --git a/src/app/components/datepicker/datepicker-demo.component.html b/src/app/components/datepicker/datepicker-demo.component.html new file mode 100644 index 000000000..be1851c23 --- /dev/null +++ b/src/app/components/datepicker/datepicker-demo.component.html @@ -0,0 +1,5 @@ + + + +Selected date is {{selectedDate}} diff --git a/src/app/components/datepicker/datepicker-demo.component.ts b/src/app/components/datepicker/datepicker-demo.component.ts new file mode 100644 index 000000000..a8d81ca30 --- /dev/null +++ b/src/app/components/datepicker/datepicker-demo.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sky-datepicker-demo', + templateUrl: './datepicker-demo.component.html' +}) +export class SkyDatepickerDemoComponent { + public selectedDate: Date; +} diff --git a/src/app/components/datepicker/index.html b/src/app/components/datepicker/index.html new file mode 100644 index 000000000..a9cb9c46f --- /dev/null +++ b/src/app/components/datepicker/index.html @@ -0,0 +1,28 @@ + + + The datepicker module contains directives for selecting dates. + + + + + Specifies the width of the label portion of the definition list. + + + Specifies the text to display when no value is available in a label-value pair. + + + + + + + + + diff --git a/src/app/components/demo-components.service.ts b/src/app/components/demo-components.service.ts index 368f9701a..e2e5188cf 100644 --- a/src/app/components/demo-components.service.ts +++ b/src/app/components/demo-components.service.ts @@ -111,6 +111,27 @@ export class SkyDemoComponentsService { ]; } }, + { + name: 'Datepicker', + icon: 'calendar', + // tslint:disable-next-line + summary: `The datepicker module contains directives for selecting dates.`, + url: '/components/datepicker', + getCodeFiles: function () { + return [ + { + name: 'datepicker-demo.component.html', + fileContents: require('!!raw!./datepicker/datepicker-demo.component.html') + }, + { + name: 'datepicker-demo.component.ts', + fileContents: require('!!raw!./datepicker/datepicker-demo.component.ts'), + componentName: 'SkyDatepickerDemoComponent', + bootstrapSelector: 'sky-datepicker-demo' + } + ]; + } + }, { name: 'Definition list', icon: 'list-alt', diff --git a/src/core.ts b/src/core.ts index 30d5a1f70..42347e95e 100644 --- a/src/core.ts +++ b/src/core.ts @@ -9,6 +9,7 @@ import { SkyCardModule } from './modules/card'; import { SkyCheckboxModule } from './modules/checkbox'; import { SkyChevronModule } from './modules/chevron'; import { SkyColumnSelectorModule } from './modules/column-selector'; +import { SkyDatepickerModule } from './modules/datepicker'; import { SkyDefinitionListModule } from './modules/definition-list'; import { SkyDropdownModule } from './modules/dropdown'; import { SkyErrorModule } from './modules/error'; @@ -80,7 +81,8 @@ import { SkyWaitModule } from './modules/wait'; SkyTextExpandRepeaterModule, SkyTilesModule, SkyToolbarModule, - SkyWaitModule + SkyWaitModule, + SkyDatepickerModule ] }) export class SkyModule { } @@ -91,6 +93,7 @@ export * from './modules/action-button'; export * from './modules/card'; export * from './modules/checkbox'; export * from './modules/column-selector'; +export * from './modules/datepicker'; export * from './modules/definition-list'; export * from './modules/dropdown'; export * from './modules/error'; diff --git a/src/modules/datepicker-calendar/datepicker-calendar.component.html b/src/modules/datepicker-calendar/datepicker-calendar.component.html deleted file mode 100644 index 440598799..000000000 --- a/src/modules/datepicker-calendar/datepicker-calendar.component.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/src/modules/datepicker-calendar/datepicker-calendar.component.ts b/src/modules/datepicker-calendar/datepicker-calendar.component.ts deleted file mode 100644 index a93fdc74d..000000000 --- a/src/modules/datepicker-calendar/datepicker-calendar.component.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { - Component, - EventEmitter, - Input, - Output, - ViewChild -} from '@angular/core'; -import { - SkyDatepickerCalendarInnerComponent -} from './datepicker-calendar-inner.component'; -import { - SkyDatepickerConfig -} from './datepicker.config'; - -/* tslint:disable:component-selector-name component-selector-type */ -@Component({ - selector: 'sky-datepicker', - templateUrl: './datepicker-calendar.component.html' -}) -export class SkyDatepickerComponent { - /** sets datepicker mode, supports: `day`, `month`, `year` */ - @Input() - public datepickerMode: string = 'day'; - /** default date to show if `ng-model` value is not specified */ - @Input() - public initDate: Date; - /** oldest selectable date */ - @Input() - public minDate: Date; - /** latest selectable date */ - @Input() - public maxDate: Date; - /** format of day in month */ - @Input() - public formatDay: string; - /** format of month in year */ - @Input() - public formatMonth: string; - /** format of year in year range */ - @Input() - public formatYear: string; - /** array of disabled dates */ - @Input() - public dateDisabled: { date: Date, mode: string }[]; - /** starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday) */ - @Input() - public startingDay: number; - - /** currently active date */ - @Input() - public get activeDate(): Date { - return this._activeDate || this._now; - } - - public set activeDate(value: Date) { - this._activeDate = value; - } - - @Output() - public selectionDone: EventEmitter = new EventEmitter(undefined); - - @Output() - public activeDateChange: EventEmitter = new EventEmitter(undefined); - - @ViewChild(SkyDatepickerCalendarInnerComponent) - public _datepicker: SkyDatepickerCalendarInnerComponent; - - public minMode: string; - public maxMode: string; - public monthColLimit: number; - public yearColLimit: number; - - protected _now: Date = new Date(); - protected _activeDate: Date; - protected config: SkyDatepickerConfig; - - public constructor(config: SkyDatepickerConfig) { - this.config = config; - this.configureOptions(); - } - - public configureOptions(): void { - Object.assign(this, this.config); - } - - public onActiveDateChange(event: Date): void { - this.activeDateChange.emit(event); - } - - public writeValue(value: any): void { - if (this._datepicker.compare(value, this._activeDate) === 0) { - return; - } - if (value && value instanceof Date) { - this.activeDate = value; - this._datepicker.select(value); - return; - } - - this.activeDate = value ? new Date(value) : void 0; - } -} diff --git a/src/modules/datepicker-calendar/datepicker.config.ts b/src/modules/datepicker-calendar/datepicker.config.ts deleted file mode 100644 index c4b269236..000000000 --- a/src/modules/datepicker-calendar/datepicker.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable() -export class SkyDatepickerConfig { - public datepickerMode: string = 'day'; - public startingDay: number = 0; - public yearRange: number = 20; - public minMode: string = 'day'; - public maxMode: string = 'year'; - public showWeeks: boolean = true; - public formatDay: string = 'DD'; - public formatMonth: string = 'MMMM'; - public formatYear: string = 'YYYY'; - public formatDayHeader: string = 'dd'; - public formatDayTitle: string = 'MMMM YYYY'; - public formatMonthTitle: string = 'YYYY'; - public onlyCurrentMonth: boolean = false; - public monthColLimit: number = 3; - public yearColLimit: number = 5; - public shortcutPropagation: boolean = false; -} diff --git a/src/modules/datepicker-calendar/yearpicker.component.html b/src/modules/datepicker-calendar/yearpicker.component.html deleted file mode 100644 index 32f491a2c..000000000 --- a/src/modules/datepicker-calendar/yearpicker.component.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - -
- - - - - -
- -
diff --git a/src/modules/datepicker-calendar/date-formatter.ts b/src/modules/datepicker/date-formatter.ts similarity index 100% rename from src/modules/datepicker-calendar/date-formatter.ts rename to src/modules/datepicker/date-formatter.ts diff --git a/src/modules/datepicker-calendar/datepicker-calendar-inner.component.html b/src/modules/datepicker/datepicker-calendar-inner.component.html similarity index 100% rename from src/modules/datepicker-calendar/datepicker-calendar-inner.component.html rename to src/modules/datepicker/datepicker-calendar-inner.component.html diff --git a/src/modules/datepicker-calendar/datepicker-calendar-inner.component.ts b/src/modules/datepicker/datepicker-calendar-inner.component.ts similarity index 58% rename from src/modules/datepicker-calendar/datepicker-calendar-inner.component.ts rename to src/modules/datepicker/datepicker-calendar-inner.component.ts index 866ede6c6..20eb09ef2 100644 --- a/src/modules/datepicker-calendar/datepicker-calendar-inner.component.ts +++ b/src/modules/datepicker/datepicker-calendar-inner.component.ts @@ -29,46 +29,48 @@ import { SkyDateFormatter } from './date-formatter'; }; */ +/** + * Monotonically increasing integer used to auto-generate unique ids for checkbox components. + */ +let nextDatepickerId = 0; + @Component({ selector: 'sky-datepicker-inner', templateUrl: './datepicker-calendar-inner.component.html' }) export class SkyDatepickerCalendarInnerComponent implements OnInit, OnChanges { - @Input() - public datepickerMode: string; @Input() public startingDay: number; - @Input() - public yearRange: number; @Input() public minDate: Date; + @Input() public maxDate: Date; - @Input() - public minMode: string; - @Input() - public maxMode: string; @Input() - public monthColLimit: number; - @Input() - public yearColLimit: number; + public selectedDate: Date; - @Input() - public formatDay: string; - @Input() - public formatMonth: string; - @Input() - public formatYear: string; + @Output() + public selectedDateChange: EventEmitter = new EventEmitter(undefined); - @Input() - public dateDisabled: { date: Date, mode: string }[]; - @Input() - public initDate: Date; + public activeDate: Date; - @Output() - public activeDateChange: EventEmitter = new EventEmitter(undefined); + public minMode: string = 'day'; + public maxMode: string = 'year'; + public monthColLimit: number = 3; + public yearColLimit: number = 5; + public datepickerMode: string = 'day'; + public yearRange: number = 20; + + public formatDay: string = 'DD'; + public formatMonth: string = 'MMMM'; + public formatYear: string = 'YYYY'; + public formatDayHeader: string = 'dd'; + public formatDayTitle: string = 'MMMM YYYY'; + public formatMonthTitle: string = 'YYYY'; + + public datepickerId: string = `sky-datepicker-${++nextDatepickerId}`; public stepDay: any = {}; public stepMonth: any = {}; @@ -76,37 +78,20 @@ export class SkyDatepickerCalendarInnerComponent implements OnInit, OnChanges { protected modes: string[] = ['day', 'month', 'year']; protected dateFormatter: SkyDateFormatter = new SkyDateFormatter(); - protected uniqueId: string; - protected _activeDate: Date; - protected selectedDate: Date; protected activeDateId: string; protected refreshViewHandlerDay: Function; - protected compareHandlerDay: Function; + public compareHandlerDay: Function; protected refreshViewHandlerMonth: Function; protected compareHandlerMonth: Function; protected refreshViewHandlerYear: Function; protected compareHandlerYear: Function; - @Input() - public get activeDate(): Date { - return this._activeDate; - } - - public set activeDate(value: Date) { - this._activeDate = value; - } - - // todo: add formatter value to Date object public ngOnInit(): void { - // todo: use date for unique value - this.uniqueId = 'datepicker-' + '-' + Math.floor(Math.random() * 10000); - - if (this.initDate) { - this.activeDate = this.initDate; - this.selectedDate = new Date(this.activeDate.valueOf() as number); - this.activeDateChange.emit(this.activeDate); - } else if (this.activeDate === undefined) { + + if (this.selectedDate) { + this.activeDate = new Date(this.selectedDate); + } else { this.activeDate = new Date(); } } @@ -190,12 +175,13 @@ export class SkyDatepickerCalendarInnerComponent implements OnInit, OnChanges { return false; } + // dateObject should probably be it's own class. split, dateFilter, and isActive might be good to have in some sort of utils service + public createDateObject(date: Date, format: string): any { let dateObject: any = {}; dateObject.date = new Date(date.getFullYear(), date.getMonth(), date.getDate()); dateObject.label = this.dateFilter(date, format); dateObject.selected = this.compare(date, this.selectedDate) === 0; - dateObject.disabled = this.isDisabled(date); dateObject.current = this.compare(date, new Date()) === 0; return dateObject; } @@ -208,33 +194,38 @@ export class SkyDatepickerCalendarInnerComponent implements OnInit, OnChanges { return arrays; } - // Fix a hard-reproducible bug with timezones - // The bug depends on OS, browser, current timezone and current date - // i.e. - // var date = new Date(2014, 0, 1); - // console.log(date.getFullYear(), date.getMonth(), date.getDate(), - // date.getHours()); can result in "2013 11 31 23" because of the bug. + /* + This is ensures that no strangeness happens when converting a date to local time. + */ public fixTimeZone(date: Date): Date { - let hours = date.getHours(); - return new Date(date.getFullYear(), date.getMonth(), date.getDate(), hours === 23 ? hours + 2 : 0); + let newDate = new Date(date); + newDate.setFullYear( + date.getFullYear(), + date.getMonth(), + date.getDate()); + + return newDate; } - public select(date: Date): void { + public select(date: Date, isManual: boolean = true): void { + + + this.activeDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); + + /* + Only actually select date if in minmode (day picker mode). Otherwise, just change the active + view for the datepicker. + */ if (this.datepickerMode === this.minMode) { - if (!this.activeDate) { - this.activeDate = new Date(0, 0, 0, 0, 0, 0, 0); + this.selectedDate = new Date(this.activeDate); + if (isManual) { + this.selectedDateChange.emit(this.selectedDate); } - this.activeDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); - } else { - - this.activeDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); this.datepickerMode = this.modes[this.modes.indexOf(this.datepickerMode) - 1]; } - this.selectedDate = new Date(this.activeDate.valueOf() as number); - this.activeDateChange.emit(this.activeDate); this.refreshView(); } @@ -253,12 +244,12 @@ export class SkyDatepickerCalendarInnerComponent implements OnInit, OnChanges { } if (expectedStep) { - let year = this.activeDate.getFullYear() + direction * (expectedStep.years || 0); - let month = this.activeDate.getMonth() + direction * (expectedStep.months || 0); + let year = this.activeDate.getFullYear() + (direction * (expectedStep.years || 0)); + let month = this.activeDate.getMonth() + (direction * (expectedStep.months || 0)); + this.activeDate = new Date(year, month, 1); this.refreshView(); - this.activeDateChange.emit(this.activeDate); } } @@ -273,39 +264,4 @@ export class SkyDatepickerCalendarInnerComponent implements OnInit, OnChanges { this.datepickerMode = this.modes[this.modes.indexOf(this.datepickerMode) + direction]; this.refreshView(); } - - - protected compareDateDisabled(date1Disabled: { date: Date, mode: string }, date2: Date): number { - if (date1Disabled === undefined || date2 === undefined) { - return undefined; - } - - if (date1Disabled.mode === 'day' && this.compareHandlerDay) { - return this.compareHandlerDay(date1Disabled.date, date2); - } - - if (date1Disabled.mode === 'month' && this.compareHandlerMonth) { - return this.compareHandlerMonth(date1Disabled.date, date2); - } - - if (date1Disabled.mode === 'year' && this.compareHandlerYear) { - return this.compareHandlerYear(date1Disabled.date, date2); - } - - return undefined; - } - - protected isDisabled(date: Date): boolean { - let isDateDisabled: boolean = false; - if (this.dateDisabled) { - this.dateDisabled.forEach((disabledDate: { date: Date, mode: string }) => { - if (this.compareDateDisabled(disabledDate, date) === 0) { - isDateDisabled = true; - } - }); - } - - return (isDateDisabled || (this.minDate && this.compare(date, this.minDate) < 0) || - (this.maxDate && this.compare(date, this.maxDate) > 0)); - } } diff --git a/src/modules/datepicker/datepicker-calendar.component.html b/src/modules/datepicker/datepicker-calendar.component.html new file mode 100644 index 000000000..e4ec0e070 --- /dev/null +++ b/src/modules/datepicker/datepicker-calendar.component.html @@ -0,0 +1,10 @@ + + + + + diff --git a/src/modules/datepicker/datepicker-calendar.component.ts b/src/modules/datepicker/datepicker-calendar.component.ts new file mode 100644 index 000000000..753be625d --- /dev/null +++ b/src/modules/datepicker/datepicker-calendar.component.ts @@ -0,0 +1,69 @@ +import { + Component, + EventEmitter, + Input, + Output, + ViewChild +} from '@angular/core'; +import { + SkyDatepickerCalendarInnerComponent +} from './datepicker-calendar-inner.component'; +import { + SkyDatepickerConfigService +} from './datepicker-config.service'; + +@Component({ + selector: 'sky-datepicker-calendar', + templateUrl: './datepicker-calendar.component.html' +}) +export class SkyDatepickerCalendarComponent { + + @Input() + public minDate: Date; + + @Input() + public maxDate: Date; + + /** starting day of the week from 0-6 (0=Sunday, ..., 6=Saturday) */ + @Input() + public startingDay: number; + + /** currently selected date */ + @Input() + public selectedDate: Date; + + @Output() + public selectedDateChange: EventEmitter = new EventEmitter(undefined); + + @ViewChild(SkyDatepickerCalendarInnerComponent) + public _datepicker: SkyDatepickerCalendarInnerComponent; + + protected _now: Date = new Date(); + protected config: SkyDatepickerConfigService; + + public constructor(config: SkyDatepickerConfigService) { + this.config = config; + this.configureOptions(); + } + + public configureOptions(): void { + Object.assign(this, this.config); + } + + public onSelectedDateChange(event: Date): void { + this.selectedDateChange.emit(event); + } + + public writeValue(value: any): void { + if (this._datepicker.compareHandlerDay(value, this.selectedDate) === 0) { + return; + } + if (value && value instanceof Date) { + this.selectedDate = value; + this._datepicker.select(value, false); + return; + } + + this.selectedDate = value ? new Date(value) : new Date(); + } +} diff --git a/src/modules/datepicker/datepicker-config.service.ts b/src/modules/datepicker/datepicker-config.service.ts new file mode 100644 index 000000000..3046ed466 --- /dev/null +++ b/src/modules/datepicker/datepicker-config.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class SkyDatepickerConfigService { + public startingDay: number = 0; + public minDate: Date; + public maxDate: Date; +} diff --git a/src/modules/datepicker/datepicker.module.ts b/src/modules/datepicker/datepicker.module.ts new file mode 100644 index 000000000..13f2f0d9d --- /dev/null +++ b/src/modules/datepicker/datepicker.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { SkyDatepickerCalendarComponent } from './datepicker-calendar.component'; +import { SkyDatepickerCalendarInnerComponent } from './datepicker-calendar-inner.component'; +import { SkyDayPickerComponent } from './daypicker.component'; +import { SkyMonthPickerComponent } from './monthpicker.component'; +import { SkyYearPickerComponent } from './yearpicker.component'; + +import { SkyDatepickerConfigService } from './datepicker-config.service'; +import { SkyResourcesModule } from '../resources'; + +@NgModule({ + declarations: [ + SkyDatepickerCalendarComponent, + SkyDatepickerCalendarInnerComponent, + SkyDayPickerComponent, + SkyMonthPickerComponent, + SkyYearPickerComponent + ], + imports: [ + CommonModule, + SkyResourcesModule + ], + exports: [ + SkyDatepickerCalendarComponent, + SkyDatepickerCalendarInnerComponent, + SkyDayPickerComponent, + SkyMonthPickerComponent, + SkyYearPickerComponent + ], + providers: [ + SkyDatepickerConfigService + ] +}) +export class SkyDatepickerModule { } diff --git a/src/modules/datepicker-calendar/daypicker.component.html b/src/modules/datepicker/daypicker.component.html similarity index 52% rename from src/modules/datepicker-calendar/daypicker.component.html rename to src/modules/datepicker/daypicker.component.html index 76e793c00..742364256 100644 --- a/src/modules/datepicker-calendar/daypicker.component.html +++ b/src/modules/datepicker/daypicker.component.html @@ -1,38 +1,38 @@ - - + @@ -40,16 +40,13 @@
-
{{labelz.abbr}}