diff --git a/README.md b/README.md index 8a0100f41..496bc26e1 100644 --- a/README.md +++ b/README.md @@ -205,22 +205,26 @@ export class HomePage { ### CalendarComponentOptions -| Name | Type | Default | Description | -| ----------------- | ----------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------- | -| from | Date | `new Date()` | start date | -| to | Date | 0 (Infinite) | end date | -| color | string | `'primary'` | 'primary', 'secondary', 'danger', 'light', 'dark' | -| pickMode | string | `single` | 'multi', 'range', 'single' | -| showToggleButtons | boolean | `true` | show toggle buttons | -| showMonthPicker | boolean | `true` | show month picker | -| monthPickerFormat | Array | `['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']` | month picker format | -| defaultTitle | string | '' | default title in days | -| defaultSubtitle | string | '' | default subtitle in days | -| disableWeeks | Array | `[]` | week to be disabled (0-6) | -| monthFormat | string | `'MMM YYYY'` | month title format | -| weekdays | Array | `['S', 'M', 'T', 'W', 'T', 'F', 'S']` | weeks text | -| weekStart | number | `0` (0 or 1) | set week start day | -| daysConfig | Array<**_DaysConfig_**> | `[]` | days configuration | +| Name | Type | Default | Description | +| ----------------- | ----------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------- | +| from | Date | `new Date()` | start date | +| to | Date | 0 (Infinite) | end date | +| color | string | `'primary'` | 'primary', 'secondary', 'danger', 'light', 'dark' | +| pickMode | string | `single` | 'multi', 'range', 'single' | +| showToggleButtons | boolean | `true` | show toggle buttons | +| showMonthPicker | boolean | `true` | show month picker | +| monthPickerFormat | Array | `['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']` | month picker format | +| defaultTitle | string | '' | default title in days | +| defaultSubtitle | string | '' | default subtitle in days | +| disableWeeks | Array | `[]` | week to be disabled (0-6) | +| monthFormat | string | `'MMM YYYY'` | month title format | +| weekdays | Array | `['S', 'M', 'T', 'W', 'T', 'F', 'S']` | weeks text | +| weekStart | number | `0` (0 or 1) | set week start day | +| daysConfig | Array<**_DaysConfig_**> | `[]` | days configuration | +| showAdjacentMonthDay | boolean | `true` | show days of other months | +| displayMode | string | `month` | 'month', 'week' | +| weeks | number | `1` | number of week to show in week display mode | +| continuous | boolean | `false` | how should be navigated when the week change | # Modal Mode diff --git a/demo/src/demos/demo-options.ts b/demo/src/demos/demo-options.ts index 55b92e107..539487a35 100644 --- a/demo/src/demos/demo-options.ts +++ b/demo/src/demos/demo-options.ts @@ -23,7 +23,7 @@ import { disableWeeks - 0 @@ -50,6 +50,34 @@ import { showMonthPicker + + showAdjacentMonthDay + + + + displayMode + + + + month   + + + + week   + + + + + + + weeks + + + + continuous + + 0){ + this.options = { + ...this.options, + weeks: weeks + } + } + } + + _changeContinuous(continuous: boolean) { + this.options = { + ...this.options, + continuous + } + } + + _changeShowAdjacentMonthDay(showAdjacentMonthDay: boolean) { + this.options = { + ...this.options, + showAdjacentMonthDay + } } } diff --git a/dev/src/demos/demo-options.ts b/dev/src/demos/demo-options.ts index 8bcb7f4c2..29a3977e6 100644 --- a/dev/src/demos/demo-options.ts +++ b/dev/src/demos/demo-options.ts @@ -21,7 +21,7 @@ import { CalendarComponentOptions } from '../ion2-calendar'; disableWeeks - 0 @@ -48,6 +48,34 @@ import { CalendarComponentOptions } from '../ion2-calendar'; showMonthPicker + + showAdjacentMonthDay + + + + displayMode + + + + month   + + + + week   + + + + + + + weeks + + + + continuous + + 0){ + this.options = { + ...this.options, + weeks: weeks, + }; + } + } + + _changeContinuous(continuous: boolean) { + this.options = { + ...this.options, + continuous, + }; + } + + _changeShowAdjacentMonthDay(showAdjacentMonthDay: boolean) { + this.options = { + ...this.options, + showAdjacentMonthDay, + }; + } } diff --git a/dev/src/ion2-calendar/calendar.model.ts b/dev/src/ion2-calendar/calendar.model.ts index 2014d79a6..6334602e4 100644 --- a/dev/src/ion2-calendar/calendar.model.ts +++ b/dev/src/ion2-calendar/calendar.model.ts @@ -90,6 +90,9 @@ export interface CalendarOptions { * show last month & next month days fill six weeks */ showAdjacentMonthDay?: boolean; + displayMode?: DisplayMode; + weeks?: number; + continuous?: boolean; } export interface CalendarComponentOptions extends CalendarOptions { @@ -113,8 +116,14 @@ export class CalendarComponentMonthChange { newMonth: CalendarResult; } +export class CalendarComponentWeekChange { + oldWeek: CalendarResult; + newWeek: CalendarResult; +} + export type DefaultDate = Date | string | number | null; export type Colors = 'primary' | 'secondary' | 'danger' | 'light' | 'dark' | string; export type PickMode = 'multi' | 'single' | 'range'; export type CalendarComponentTypeProperty = 'string' | 'js-date' | 'moment' | 'time' | 'object'; export type CalendarComponentPayloadTypes = string | Date | number | {}; +export type DisplayMode = 'month' | 'week'; diff --git a/dev/src/ion2-calendar/components/calendar.component.ts b/dev/src/ion2-calendar/components/calendar.component.ts index 41750aca4..3e3d3fdfb 100755 --- a/dev/src/ion2-calendar/components/calendar.component.ts +++ b/dev/src/ion2-calendar/components/calendar.component.ts @@ -8,6 +8,7 @@ import { CalendarComponentPayloadTypes, CalendarComponentMonthChange, CalendarComponentTypeProperty, + DisplayMode, CalendarComponentWeekChange, } from '../calendar.model'; import { CalendarService } from '../services/calendar.service'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @@ -127,6 +128,8 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { @Output() monthChange: EventEmitter = new EventEmitter(); @Output() + weekChange: EventEmitter = new EventEmitter(); + @Output() select: EventEmitter = new EventEmitter(); @Output() selectStart: EventEmitter = new EventEmitter(); @@ -138,7 +141,7 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this._options = value; this.initOpt(); if (this.monthOpt && this.monthOpt.original) { - this.monthOpt = this.createMonth(this.monthOpt.original.time); + this.createWeekOrMonth(this.monthOpt.original.time); } } @@ -168,7 +171,7 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { ngOnInit(): void { this.initOpt(); - this.monthOpt = this.createMonth(new Date().getTime()); + this.createWeekOrMonth(new Date().getTime()); } getViewDate() { @@ -180,7 +183,7 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { } setViewDate(value: CalendarComponentPayloadTypes) { - this.monthOpt = this.createMonth(this._payloadToTimeNumber(value)); + this.createWeekOrMonth(this._payloadToTimeNumber(value)); } switchView(): void { @@ -189,7 +192,11 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { prev(): void { if (this._view === 'days') { - this.backMonth(); + if (this._d.displayMode === 'week') { + this.backWeek(); + } else { + this.backMonth(); + } } else { this.prevYear(); } @@ -197,7 +204,11 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { next(): void { if (this._view === 'days') { - this.nextMonth(); + if (this._d.displayMode === 'week') { + this.nextWeek(); + } else { + this.nextMonth(); + } } else { this.nextYear(); } @@ -208,14 +219,14 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { const backTime = moment(this.monthOpt.original.time) .subtract(1, 'year') .valueOf(); - this.monthOpt = this.createMonth(backTime); + this.createWeekOrMonth(backTime); } nextYear(): void { const nextTime = moment(this.monthOpt.original.time) .add(1, 'year') .valueOf(); - this.monthOpt = this.createMonth(nextTime); + this.createWeekOrMonth(nextTime); } nextMonth(): void { @@ -229,6 +240,30 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this.monthOpt = this.createMonth(nextTime); } + nextWeek(): void { + let nextTime = moment(this.monthOpt.original.time) + .add(this._d.weeks, 'weeks') + .valueOf(); + let oldWeek = this.calSvc.multiFormat(this.monthOpt.original.time); + let newWeek = this.calSvc.multiFormat(nextTime); + if (oldWeek.months != newWeek.months && !this._d.continuous) { + let _start = new Date(nextTime); + nextTime = new Date(_start.getFullYear(), _start.getMonth(), 1).getTime(); + newWeek = this.calSvc.multiFormat(nextTime); + } + this.monthOpt = this.createWeek(nextTime); + this.weekChange.emit({ + oldWeek: oldWeek, + newWeek: this.calSvc.multiFormat(this.monthOpt.original.time), + }); + if (oldWeek.months != newWeek.months) { + this.monthChange.emit({ + oldMonth: oldWeek, + newMonth: this.calSvc.multiFormat(this.monthOpt.original.time), + }); + } + } + canNext(): boolean { if (!this._d.to || this._view !== 'days') { return true; } return this.monthOpt.original.time < moment(this._d.to).valueOf(); @@ -245,6 +280,44 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this.monthOpt = this.createMonth(backTime); } + backWeek(): void { + let backTime = moment(this.monthOpt.original.time) + .subtract(this._d.weeks, 'weeks') + .valueOf(); + let oldWeek = this.calSvc.multiFormat(this.monthOpt.original.time); + let newWeek = this.calSvc.multiFormat(backTime); + if (oldWeek.months != newWeek.months && !this._d.continuous) { + let _start = new Date(this.monthOpt.original.time); + let dayToSubstrac = _start.getDay(); + if (this.options.weekStart === 1) { + dayToSubstrac--; + if (dayToSubstrac < 0) { + dayToSubstrac = 6; + } + } + + let firstDayMonth = new Date(_start.getFullYear(), _start.getMonth(), 1).getTime(); + let momentBackTime = moment(firstDayMonth); + if (_start.getDate() - dayToSubstrac <= 1) { + momentBackTime = momentBackTime.subtract(1, 'd'); + } + backTime = momentBackTime.valueOf(); + + newWeek = this.calSvc.multiFormat(backTime); + } + this.weekChange.emit({ + oldWeek: oldWeek, + newWeek: newWeek, + }); + if (oldWeek.months != newWeek.months) { + this.monthChange.emit({ + oldMonth: oldWeek, + newMonth: newWeek, + }); + } + this.monthOpt = this.createWeek(backTime); + } + canBack(): boolean { if (!this._d.from || this._view !== 'days') { return true; } return this.monthOpt.original.time > moment(this._d.from).valueOf(); @@ -259,7 +332,7 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { oldMonth: this.calSvc.multiFormat(this.monthOpt.original.time), newMonth: this.calSvc.multiFormat(newMonth), }); - this.monthOpt = this.createMonth(newMonth); + this.createWeekOrMonth(newMonth); } onChanged($event: CalendarDay[]): void { @@ -338,10 +411,22 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this._d = this.calSvc.safeOpt(this._options || {}); } + private createWeekOrMonth(time: number) { + if (this._d.displayMode === 'week') { + this.monthOpt = this.createWeek(time); + } else { + this.monthOpt = this.createMonth(time); + } + } + createMonth(date: number): CalendarMonth { return this.calSvc.createMonthsByPeriod(date, 1, this._d)[0]; } + createWeek(date: number): CalendarMonth { + return this.calSvc.createWeeksByPeriod(date, this._d)[0]; + } + _createCalendarDay(value: CalendarComponentPayloadTypes): CalendarDay { return this.calSvc.createCalendarDay(this._payloadToTimeNumber(value), this._d); } @@ -367,9 +452,9 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this._writeValue(obj); if (obj) { if (this._calendarMonthValue[0]) { - this.monthOpt = this.createMonth(this._calendarMonthValue[0].time); + this.createWeekOrMonth(this._calendarMonthValue[0].time); } else { - this.monthOpt = this.createMonth(new Date().getTime()); + this.createWeekOrMonth(new Date().getTime()); } } } diff --git a/dev/src/ion2-calendar/config.ts b/dev/src/ion2-calendar/config.ts index c6261ca47..c7610d998 100644 --- a/dev/src/ion2-calendar/config.ts +++ b/dev/src/ion2-calendar/config.ts @@ -10,3 +10,8 @@ export const pickModes = { RANGE: 'range', MULTI: 'multi' }; + +export const displayModes = { + WEEK: 'week', + MONTH: 'month' +}; diff --git a/dev/src/ion2-calendar/services/calendar.service.ts b/dev/src/ion2-calendar/services/calendar.service.ts index 6c21df67e..8c4370b11 100644 --- a/dev/src/ion2-calendar/services/calendar.service.ts +++ b/dev/src/ion2-calendar/services/calendar.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, Optional } from '@angular/core'; +import {Inject, Injectable, Optional} from '@angular/core'; import * as moment from 'moment'; import { @@ -9,8 +9,8 @@ import { CalendarResult, DayConfig, } from '../calendar.model'; -import { defaults, pickModes } from '../config'; -import { DEFAULT_CALENDAR_OPTIONS } from './calendar-options.provider'; +import {defaults, displayModes, pickModes} from '../config'; +import {DEFAULT_CALENDAR_OPTIONS} from './calendar-options.provider'; const isBoolean = (input: any) => input === true || input === false; @@ -56,7 +56,10 @@ export class CalendarService { showAdjacentMonthDay = true, defaultEndDateToStartDate = false, clearLabel = null, - } = { ...this.defaultOpts, ...calendarOptions }; + displayMode = displayModes.MONTH, + weeks = 1, + continuous = false + } = {...this.defaultOpts, ...calendarOptions}; return { id, @@ -88,11 +91,14 @@ export class CalendarService { defaultDateRange: calendarOptions.defaultDateRange || null, showAdjacentMonthDay, defaultEndDateToStartDate, - clearLabel + clearLabel, + displayMode, + weeks, + continuous }; } - createOriginalCalendar(time: number): CalendarOriginal { + createOriginalCalendar(time: number, timeWithDay = false): CalendarOriginal { const date = new Date(time); const year = date.getFullYear(); const month = date.getMonth(); @@ -103,13 +109,15 @@ export class CalendarService { month, firstWeek, howManyDays, - time: new Date(year, month, 1).getTime(), + time: new Date(year, month, (timeWithDay) ? date.getDate() : 1).getTime(), date: new Date(time), }; } findDayConfig(day: any, opt: CalendarModalOptions): any { - if (opt.daysConfig.length <= 0) return null; + if (opt.daysConfig.length <= 0) { + return null; + } return opt.daysConfig.find(n => day.isSame(n.date, 'day')); } @@ -237,6 +245,132 @@ export class CalendarService { return _array; } + createCalendarMonthOfWeek(original: CalendarOriginal, opt: CalendarModalOptions): CalendarMonth { + let days: Array = new Array(6).fill(null); + let len = 7 * opt.weeks; + let originalDate = original.date; + let startWeek; + let startDay; + let startIndex = 0; + + if (opt.continuous) { + if (originalDate.getDay() === 0) { + if (opt.weekStart === 0) { + startWeek = 0; + startDay = originalDate.getDate(); + } else { + startWeek = 1; + startDay = originalDate.getDate() - 6; + } + } else { + if (opt.weekStart === 0) { + startWeek = 0; + startDay = originalDate.getDate() - originalDate.getDay(); + } else { + startWeek = 1; + startDay = originalDate.getDate() - (originalDate.getDay() - 1); + } + } + } else { + if (originalDate.getDay() === 0) { + if (opt.weekStart === 0) { + startWeek = 0; + startDay = originalDate.getDate(); + } else { + if (originalDate.getDate() - 6 > 1) { + startWeek = 1; + startDay = originalDate.getDate() - 6; + } else { + startWeek = original.firstWeek; + startDay = 1; + startIndex = startWeek - 1; + if (startIndex < 0) { + startIndex = 6; + } + } + } + } else { + if (opt.weekStart === 0) { + if (originalDate.getDate() - originalDate.getDay() > 1) { + startWeek = 0; + startDay = originalDate.getDate() - originalDate.getDay(); + } else { + startWeek = original.firstWeek; + startDay = 1; + startIndex = startWeek; + } + } else { + if (originalDate.getDate() - (originalDate.getDay() - 1) > 1) { + startWeek = 1; + startDay = originalDate.getDate() - (originalDate.getDay() - 1); + } else { + startWeek = original.firstWeek; + startDay = 1; + startIndex = startWeek - 1; + } + } + } + } + + + for (let i = startIndex; i < 7 * opt.weeks && (opt.continuous || startDay + (i - startIndex) <= original.howManyDays); i++) { + let itemTime = new Date(original.year, original.month, startDay + (i - startIndex)).getTime(); + days[i] = this.createCalendarDay(itemTime, opt); + } + + while (!opt.continuous && days.length <= 7 * (opt.weeks - 1) && startDay != 1) { + days.unshift(...new Array(7).fill(null)); + let i = 6; + while (i >= 0 && startDay != 1) { + startDay--; + let itemTime = new Date(original.year, original.month, startDay).getTime(); + days[i] = this.createCalendarDay(itemTime, opt); + i--; + } + original.date.setDate(startDay); + original.time = new Date(original.date).getTime(); + } + + if (opt.showAdjacentMonthDay) { + const _booleanMap = days.map(e => !!e); + const thisMonth = moment(original.time).month(); + let startOffsetIndex = _booleanMap.indexOf(true) - 1; + let endOffsetIndex = _booleanMap.lastIndexOf(true) + 1; + for (startOffsetIndex; startOffsetIndex >= 0; startOffsetIndex--) { + const dayBefore = moment(days[startOffsetIndex + 1].time) + .clone() + .subtract(1, 'd'); + days[startOffsetIndex] = this.createCalendarDay(dayBefore.valueOf(), opt, thisMonth); + } + + if (!(_booleanMap.length % 7 === 0 && _booleanMap[_booleanMap.length - 1])) { + for (endOffsetIndex; endOffsetIndex < days.length + (endOffsetIndex % 7); endOffsetIndex++) { + const dayAfter = moment(days[endOffsetIndex - 1].time) + .clone() + .add(1, 'd'); + days[endOffsetIndex] = this.createCalendarDay(dayAfter.valueOf(), opt, thisMonth); + } + } + } + + return { + days, + original: original, + }; + } + + createWeeksByPeriod(startTime: number, opt: CalendarModalOptions): Array { + let _array: Array = []; + + let _start = new Date(startTime); + let _startDay: any = new Date(_start.getFullYear(), _start.getMonth(), _start.getDate()).getTime(); + + let time = moment(_startDay).valueOf(); + let originalCalendar = this.createOriginalCalendar(time, true); + _array.push(this.createCalendarMonthOfWeek(originalCalendar, opt)); + return _array; + } + wrapResult(original: CalendarDay[], pickMode: string) { let result: any; switch (pickMode) { diff --git a/src/calendar.model.ts b/src/calendar.model.ts index 2014d79a6..6334602e4 100644 --- a/src/calendar.model.ts +++ b/src/calendar.model.ts @@ -90,6 +90,9 @@ export interface CalendarOptions { * show last month & next month days fill six weeks */ showAdjacentMonthDay?: boolean; + displayMode?: DisplayMode; + weeks?: number; + continuous?: boolean; } export interface CalendarComponentOptions extends CalendarOptions { @@ -113,8 +116,14 @@ export class CalendarComponentMonthChange { newMonth: CalendarResult; } +export class CalendarComponentWeekChange { + oldWeek: CalendarResult; + newWeek: CalendarResult; +} + export type DefaultDate = Date | string | number | null; export type Colors = 'primary' | 'secondary' | 'danger' | 'light' | 'dark' | string; export type PickMode = 'multi' | 'single' | 'range'; export type CalendarComponentTypeProperty = 'string' | 'js-date' | 'moment' | 'time' | 'object'; export type CalendarComponentPayloadTypes = string | Date | number | {}; +export type DisplayMode = 'month' | 'week'; diff --git a/src/components/calendar.component.ts b/src/components/calendar.component.ts index 41750aca4..3e3d3fdfb 100755 --- a/src/components/calendar.component.ts +++ b/src/components/calendar.component.ts @@ -8,6 +8,7 @@ import { CalendarComponentPayloadTypes, CalendarComponentMonthChange, CalendarComponentTypeProperty, + DisplayMode, CalendarComponentWeekChange, } from '../calendar.model'; import { CalendarService } from '../services/calendar.service'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @@ -127,6 +128,8 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { @Output() monthChange: EventEmitter = new EventEmitter(); @Output() + weekChange: EventEmitter = new EventEmitter(); + @Output() select: EventEmitter = new EventEmitter(); @Output() selectStart: EventEmitter = new EventEmitter(); @@ -138,7 +141,7 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this._options = value; this.initOpt(); if (this.monthOpt && this.monthOpt.original) { - this.monthOpt = this.createMonth(this.monthOpt.original.time); + this.createWeekOrMonth(this.monthOpt.original.time); } } @@ -168,7 +171,7 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { ngOnInit(): void { this.initOpt(); - this.monthOpt = this.createMonth(new Date().getTime()); + this.createWeekOrMonth(new Date().getTime()); } getViewDate() { @@ -180,7 +183,7 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { } setViewDate(value: CalendarComponentPayloadTypes) { - this.monthOpt = this.createMonth(this._payloadToTimeNumber(value)); + this.createWeekOrMonth(this._payloadToTimeNumber(value)); } switchView(): void { @@ -189,7 +192,11 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { prev(): void { if (this._view === 'days') { - this.backMonth(); + if (this._d.displayMode === 'week') { + this.backWeek(); + } else { + this.backMonth(); + } } else { this.prevYear(); } @@ -197,7 +204,11 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { next(): void { if (this._view === 'days') { - this.nextMonth(); + if (this._d.displayMode === 'week') { + this.nextWeek(); + } else { + this.nextMonth(); + } } else { this.nextYear(); } @@ -208,14 +219,14 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { const backTime = moment(this.monthOpt.original.time) .subtract(1, 'year') .valueOf(); - this.monthOpt = this.createMonth(backTime); + this.createWeekOrMonth(backTime); } nextYear(): void { const nextTime = moment(this.monthOpt.original.time) .add(1, 'year') .valueOf(); - this.monthOpt = this.createMonth(nextTime); + this.createWeekOrMonth(nextTime); } nextMonth(): void { @@ -229,6 +240,30 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this.monthOpt = this.createMonth(nextTime); } + nextWeek(): void { + let nextTime = moment(this.monthOpt.original.time) + .add(this._d.weeks, 'weeks') + .valueOf(); + let oldWeek = this.calSvc.multiFormat(this.monthOpt.original.time); + let newWeek = this.calSvc.multiFormat(nextTime); + if (oldWeek.months != newWeek.months && !this._d.continuous) { + let _start = new Date(nextTime); + nextTime = new Date(_start.getFullYear(), _start.getMonth(), 1).getTime(); + newWeek = this.calSvc.multiFormat(nextTime); + } + this.monthOpt = this.createWeek(nextTime); + this.weekChange.emit({ + oldWeek: oldWeek, + newWeek: this.calSvc.multiFormat(this.monthOpt.original.time), + }); + if (oldWeek.months != newWeek.months) { + this.monthChange.emit({ + oldMonth: oldWeek, + newMonth: this.calSvc.multiFormat(this.monthOpt.original.time), + }); + } + } + canNext(): boolean { if (!this._d.to || this._view !== 'days') { return true; } return this.monthOpt.original.time < moment(this._d.to).valueOf(); @@ -245,6 +280,44 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this.monthOpt = this.createMonth(backTime); } + backWeek(): void { + let backTime = moment(this.monthOpt.original.time) + .subtract(this._d.weeks, 'weeks') + .valueOf(); + let oldWeek = this.calSvc.multiFormat(this.monthOpt.original.time); + let newWeek = this.calSvc.multiFormat(backTime); + if (oldWeek.months != newWeek.months && !this._d.continuous) { + let _start = new Date(this.monthOpt.original.time); + let dayToSubstrac = _start.getDay(); + if (this.options.weekStart === 1) { + dayToSubstrac--; + if (dayToSubstrac < 0) { + dayToSubstrac = 6; + } + } + + let firstDayMonth = new Date(_start.getFullYear(), _start.getMonth(), 1).getTime(); + let momentBackTime = moment(firstDayMonth); + if (_start.getDate() - dayToSubstrac <= 1) { + momentBackTime = momentBackTime.subtract(1, 'd'); + } + backTime = momentBackTime.valueOf(); + + newWeek = this.calSvc.multiFormat(backTime); + } + this.weekChange.emit({ + oldWeek: oldWeek, + newWeek: newWeek, + }); + if (oldWeek.months != newWeek.months) { + this.monthChange.emit({ + oldMonth: oldWeek, + newMonth: newWeek, + }); + } + this.monthOpt = this.createWeek(backTime); + } + canBack(): boolean { if (!this._d.from || this._view !== 'days') { return true; } return this.monthOpt.original.time > moment(this._d.from).valueOf(); @@ -259,7 +332,7 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { oldMonth: this.calSvc.multiFormat(this.monthOpt.original.time), newMonth: this.calSvc.multiFormat(newMonth), }); - this.monthOpt = this.createMonth(newMonth); + this.createWeekOrMonth(newMonth); } onChanged($event: CalendarDay[]): void { @@ -338,10 +411,22 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this._d = this.calSvc.safeOpt(this._options || {}); } + private createWeekOrMonth(time: number) { + if (this._d.displayMode === 'week') { + this.monthOpt = this.createWeek(time); + } else { + this.monthOpt = this.createMonth(time); + } + } + createMonth(date: number): CalendarMonth { return this.calSvc.createMonthsByPeriod(date, 1, this._d)[0]; } + createWeek(date: number): CalendarMonth { + return this.calSvc.createWeeksByPeriod(date, this._d)[0]; + } + _createCalendarDay(value: CalendarComponentPayloadTypes): CalendarDay { return this.calSvc.createCalendarDay(this._payloadToTimeNumber(value), this._d); } @@ -367,9 +452,9 @@ export class CalendarComponent implements ControlValueAccessor, OnInit { this._writeValue(obj); if (obj) { if (this._calendarMonthValue[0]) { - this.monthOpt = this.createMonth(this._calendarMonthValue[0].time); + this.createWeekOrMonth(this._calendarMonthValue[0].time); } else { - this.monthOpt = this.createMonth(new Date().getTime()); + this.createWeekOrMonth(new Date().getTime()); } } } diff --git a/src/config.ts b/src/config.ts index c6261ca47..c7610d998 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,3 +10,8 @@ export const pickModes = { RANGE: 'range', MULTI: 'multi' }; + +export const displayModes = { + WEEK: 'week', + MONTH: 'month' +}; diff --git a/src/services/calendar.service.ts b/src/services/calendar.service.ts index 6c21df67e..8c4370b11 100644 --- a/src/services/calendar.service.ts +++ b/src/services/calendar.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, Optional } from '@angular/core'; +import {Inject, Injectable, Optional} from '@angular/core'; import * as moment from 'moment'; import { @@ -9,8 +9,8 @@ import { CalendarResult, DayConfig, } from '../calendar.model'; -import { defaults, pickModes } from '../config'; -import { DEFAULT_CALENDAR_OPTIONS } from './calendar-options.provider'; +import {defaults, displayModes, pickModes} from '../config'; +import {DEFAULT_CALENDAR_OPTIONS} from './calendar-options.provider'; const isBoolean = (input: any) => input === true || input === false; @@ -56,7 +56,10 @@ export class CalendarService { showAdjacentMonthDay = true, defaultEndDateToStartDate = false, clearLabel = null, - } = { ...this.defaultOpts, ...calendarOptions }; + displayMode = displayModes.MONTH, + weeks = 1, + continuous = false + } = {...this.defaultOpts, ...calendarOptions}; return { id, @@ -88,11 +91,14 @@ export class CalendarService { defaultDateRange: calendarOptions.defaultDateRange || null, showAdjacentMonthDay, defaultEndDateToStartDate, - clearLabel + clearLabel, + displayMode, + weeks, + continuous }; } - createOriginalCalendar(time: number): CalendarOriginal { + createOriginalCalendar(time: number, timeWithDay = false): CalendarOriginal { const date = new Date(time); const year = date.getFullYear(); const month = date.getMonth(); @@ -103,13 +109,15 @@ export class CalendarService { month, firstWeek, howManyDays, - time: new Date(year, month, 1).getTime(), + time: new Date(year, month, (timeWithDay) ? date.getDate() : 1).getTime(), date: new Date(time), }; } findDayConfig(day: any, opt: CalendarModalOptions): any { - if (opt.daysConfig.length <= 0) return null; + if (opt.daysConfig.length <= 0) { + return null; + } return opt.daysConfig.find(n => day.isSame(n.date, 'day')); } @@ -237,6 +245,132 @@ export class CalendarService { return _array; } + createCalendarMonthOfWeek(original: CalendarOriginal, opt: CalendarModalOptions): CalendarMonth { + let days: Array = new Array(6).fill(null); + let len = 7 * opt.weeks; + let originalDate = original.date; + let startWeek; + let startDay; + let startIndex = 0; + + if (opt.continuous) { + if (originalDate.getDay() === 0) { + if (opt.weekStart === 0) { + startWeek = 0; + startDay = originalDate.getDate(); + } else { + startWeek = 1; + startDay = originalDate.getDate() - 6; + } + } else { + if (opt.weekStart === 0) { + startWeek = 0; + startDay = originalDate.getDate() - originalDate.getDay(); + } else { + startWeek = 1; + startDay = originalDate.getDate() - (originalDate.getDay() - 1); + } + } + } else { + if (originalDate.getDay() === 0) { + if (opt.weekStart === 0) { + startWeek = 0; + startDay = originalDate.getDate(); + } else { + if (originalDate.getDate() - 6 > 1) { + startWeek = 1; + startDay = originalDate.getDate() - 6; + } else { + startWeek = original.firstWeek; + startDay = 1; + startIndex = startWeek - 1; + if (startIndex < 0) { + startIndex = 6; + } + } + } + } else { + if (opt.weekStart === 0) { + if (originalDate.getDate() - originalDate.getDay() > 1) { + startWeek = 0; + startDay = originalDate.getDate() - originalDate.getDay(); + } else { + startWeek = original.firstWeek; + startDay = 1; + startIndex = startWeek; + } + } else { + if (originalDate.getDate() - (originalDate.getDay() - 1) > 1) { + startWeek = 1; + startDay = originalDate.getDate() - (originalDate.getDay() - 1); + } else { + startWeek = original.firstWeek; + startDay = 1; + startIndex = startWeek - 1; + } + } + } + } + + + for (let i = startIndex; i < 7 * opt.weeks && (opt.continuous || startDay + (i - startIndex) <= original.howManyDays); i++) { + let itemTime = new Date(original.year, original.month, startDay + (i - startIndex)).getTime(); + days[i] = this.createCalendarDay(itemTime, opt); + } + + while (!opt.continuous && days.length <= 7 * (opt.weeks - 1) && startDay != 1) { + days.unshift(...new Array(7).fill(null)); + let i = 6; + while (i >= 0 && startDay != 1) { + startDay--; + let itemTime = new Date(original.year, original.month, startDay).getTime(); + days[i] = this.createCalendarDay(itemTime, opt); + i--; + } + original.date.setDate(startDay); + original.time = new Date(original.date).getTime(); + } + + if (opt.showAdjacentMonthDay) { + const _booleanMap = days.map(e => !!e); + const thisMonth = moment(original.time).month(); + let startOffsetIndex = _booleanMap.indexOf(true) - 1; + let endOffsetIndex = _booleanMap.lastIndexOf(true) + 1; + for (startOffsetIndex; startOffsetIndex >= 0; startOffsetIndex--) { + const dayBefore = moment(days[startOffsetIndex + 1].time) + .clone() + .subtract(1, 'd'); + days[startOffsetIndex] = this.createCalendarDay(dayBefore.valueOf(), opt, thisMonth); + } + + if (!(_booleanMap.length % 7 === 0 && _booleanMap[_booleanMap.length - 1])) { + for (endOffsetIndex; endOffsetIndex < days.length + (endOffsetIndex % 7); endOffsetIndex++) { + const dayAfter = moment(days[endOffsetIndex - 1].time) + .clone() + .add(1, 'd'); + days[endOffsetIndex] = this.createCalendarDay(dayAfter.valueOf(), opt, thisMonth); + } + } + } + + return { + days, + original: original, + }; + } + + createWeeksByPeriod(startTime: number, opt: CalendarModalOptions): Array { + let _array: Array = []; + + let _start = new Date(startTime); + let _startDay: any = new Date(_start.getFullYear(), _start.getMonth(), _start.getDate()).getTime(); + + let time = moment(_startDay).valueOf(); + let originalCalendar = this.createOriginalCalendar(time, true); + _array.push(this.createCalendarMonthOfWeek(originalCalendar, opt)); + return _array; + } + wrapResult(original: CalendarDay[], pickMode: string) { let result: any; switch (pickMode) {