diff --git a/.gitignore b/.gitignore index ed8c74bc..9679002f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /typings /node_modules - +/.idea /dist +/aot diff --git a/README.md b/README.md index fc6023a0..c8b93d82 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ intl 1.2.5, due to issue https://github.com/angular/angular/issues/3333 version 0.1.x depends on Ionic 2.0.0-rc.1 ~ Ionic 2.0.0-rc.4 version 0.2.x depends on Ionic 2.0.0-rc.5 (rc.5 has breaking change on the slide API) and 2.0.0 final version onwards. +version 0.2.9+ depends on Ionic 2.3.0 version onwards. +version 0.3.x depends on Ionic 3.1.1 version onwards. +version 0.4.x depends on Ionic 3.9.2 version onwards. # Usage @@ -19,7 +22,7 @@ Install: `npm install ionic2-calendar --save` Import the ionic2-calendar module: -``` +``` typescript import { NgModule } from '@angular/core'; import { IonicApp, IonicModule } from 'ionic-angular'; import { MyApp } from './app/app.component'; @@ -42,10 +45,30 @@ import { NgCalendarModule } from 'ionic2-calendar'; export class AppModule {} ``` -Add the directive in the html page +If you are using PageModule, you need to import the NgCalendarModule in your page module + +``` typescript +import { NgCalendarModule } from 'ionic2-calendar'; +@NgModule({ + declarations: [ + MyPage + ], + imports: [ + IonicPageModule.forChild(MyPage), + NgCalendarModule + ], + entryComponents: [ + MyPage + ] +}) +export class MyPageModule {} ``` - - + + ``` # Note for Ionic Build/Run command @@ -65,7 +89,7 @@ So the workaround is to import them explicitly. Add below lines in *main.prod.ts* -``` +``` typescript import { CalendarComponent } from 'ionic2-calendar/calendar'; import { MonthViewComponent } from 'ionic2-calendar/monthview'; import { WeekViewComponent } from 'ionic2-calendar/weekview'; @@ -86,7 +110,7 @@ The format of the title displayed in the day view. Default value: 'MMMM dd, yyyy' * formatWeekTitle The format of the title displayed in the week view. -Default value: 'MMMM yyyy, Week w' +Default value: (version 0.1-0.3) 'MMMM yyyy, Week $n', (version 0.4) 'MMMM yyyy, \'Week\' w' * formatMonthTitle The format of the title displayed in the month view. Default value: 'MMMM yyyy' @@ -95,7 +119,7 @@ The format of the header displayed in the week view. Default value: 'EEE d' * formatHourColumn The format of the hour column displayed in the week and day view. -Default value: ‘j’ +Default value: (version 0.1-0.3) 'j', (version 0.4) 'ha' * calendarMode The initial mode of the calendar. Default value: 'month' @@ -110,10 +134,10 @@ Control week view starting from which day. Default value: 0 * allDayLabel The text displayed in the allDay column header. -Default value: ‘all day’ +Default value: 'all day' * noEventsLabel The text displayed when there’s no event on the selected date in month view. -Default value: ‘No Events’ +Default value: 'No Events' * eventSource The data source of the calendar, when the eventSource is set, the view will be updated accordingly. Default value: null @@ -124,144 +148,413 @@ If queryMode is set to 'remote', when the range or mode is changed, the calendar Users will need to implement their custom loading data logic in this function, and fill it into the eventSource. The eventSource is watched, so the view will be updated once the eventSource is changed. Default value: 'local' * step -It can be set to 15 or 30, so that the event can be displayed at more accurate position in weekview or dayview. +It is used to display the event using more accurate time interval in weekview and dayview. For example, if set to 30, then the event will only occupy half of the row height (If timeInterval option uses default value). The unit is minute. It can be set to 15 or 30. Default value: 60 +``` html + +``` + +* timeInterval (version >= 0.3) +It is used to display the rows using more accurate time interval in weekview and dayview. For example, if set to 30, then the time interval between each row is 30 mins. +The unit is minute. It should be the factor or multiple of 60, which means 60%timeInterval=0 or timeInterval%60=0. +Default value: 60 +``` html + +``` + * autoSelect -If set to true, the current calendar date will be auto selected when calendar is loaded or swiped in the month view. +If set to true, the current calendar date will be auto selected when calendar is loaded or swiped in the month and week view. Default value: true +* locale +The locale used to display text in the calendar. +Default value: undefined (which means the local language) +``` html + +``` +``` json + calendar = { + locale: 'en-GB' + }; +``` * markDisabled The callback function used to determine if the time should be marked as disabled. +``` html + +``` +``` typescript + markDisabled = (date: Date) => { + var current = new Date(); + return date < current; + }; +``` +* dateFormatter +The custom date formatter to transform date to text. +If the custom date formatter is not set, the default Angular DatePipe is used. +The format method in dateFormatter is optional, if omitted, the default Angular DatePipe is used. +``` html + +``` +``` typescript + calendar = { + dateFormatter: { + formatMonthViewDay: function(date:Date) { + return date.getDate().toString(); + }, + formatMonthViewDayHeader: function(date:Date) { + return 'testMDH'; + }, + formatMonthViewTitle: function(date:Date) { + return 'testMT'; + }, + formatWeekViewDayHeader: function(date:Date) { + return 'testWDH'; + }, + formatWeekViewTitle: function(date:Date) { + return 'testWT'; + }, + formatWeekViewHourColumn: function(date:Date) { + return 'testWH'; + }, + formatDayViewHourColumn: function(date:Date) { + return 'testDH'; + }, + formatDayViewTitle: function(date:Date) { + return 'testDT'; + } + } + }; +``` +* dir +If set to "rtl", the calendar supports RTL language. This feature is only supported in Ionic 2.3.0 version onwards. +Default value: "" - +* scrollToHour +Make weekview and dayview scroll to the specific hour after entering to the new view. +Default value: 0 - markDisabled = (date: Date) => { - var current = new Date(); - return date < current; - }; +* preserveScrollPosition +If set to true, the previous/next views in weekview and dayview will also scroll to the same position as the current active view. +Default value: false -* onCurrentDateChanged -The callback function triggered when the date that is currently viewed changes. +* lockSwipeToPrev +If set to true, swiping to previous view is disabled. +Default value: false +``` html + +``` +``` typescript + onCurrentDateChanged(event:Date) { + var today = new Date(); + today.setHours(0, 0, 0, 0); + event.setHours(0, 0, 0, 0); + + if (this.calendar.mode === 'month') { + if (event.getFullYear() < today.getFullYear() || (event.getFullYear() === today.getFullYear() && event.getMonth() <= today.getMonth())) { + this.lockSwipeToPrev = true; + } else { + this.lockSwipeToPrev = false; + } + } + } +``` - +* lockSwipes +If set to true, swiping is disabled. +Default value: false +*Note:* Since swiping is disabled, you could set currentDate to move the calendar to previous/next view. Do not set lockSwipeToPrev in the constructor phase. It will cause the view not updating when changing the currentDate. You could either set it in some callback function after initialization phase or use setTimeout to trigger some delay. +``` html + +``` +``` typescript + ngAfterViewInit() { + var me = this; + setTimeout(function() { + me.lockSwipes = true; + },100); + } +``` - onCurrentChanged = (ev: Date) => { - console.log('Currently viewed date: ' + ev); - }; +* startHour +Limit the weekview and dayview starts from which hour (0-23). +Default value: 0 +``` html + +``` + +* endHour +Limit the weekview and dayview ends until which hour (1-24). +Default value: 24 +``` html + +``` +* spaceBetween +Distance between slides. +Default value: 0 +``` html + +``` +* onCurrentDateChanged +The callback function triggered when the date that is currently viewed changes. +``` html + +``` +``` typescript + onCurrentChanged = (ev: Date) => { + console.log('Currently viewed date: ' + ev); + }; +``` * onRangeChanged The callback function triggered when the range or mode is changed if the queryMode is set to 'remote' The ev parameter contains two fields, startTime and endTime. +``` html + +``` +``` typescript + onRangeChanged = (ev: { startTime: Date, endTime: Date }) => { + Events.query(ev, (events) => { + this.eventSource = events; + }); + }; +``` +* onEventSelected +The callback function triggered when an event is clicked +``` html + +``` +``` typescript + onEventSelected = (event) => { + console.log(event.title); + }; +``` +* onTimeSelected +The callback function triggered when a date is selected in the monthview. +The ev parameter contains two fields, selectedTime and events, if there's no event at the selected time, the events field will be either undefined or empty array +``` html + +``` +``` typescript + onTimeSelected = (ev: { selectedTime: Date, events: any[] }) => { + console.log('Selected time: ' + ev.selectedTime + ', hasEvents: ' + (ev.events !== undefined && ev.events.length !== 0)); + }; +``` +* onTitleChanged +The callback function triggered when the view title is changed +``` html + +``` +``` typescript + onViewTitleChanged = (title: string) => { + this.viewTitle = title; + }; +``` +# View Customization Option +There are two ways to customize the look and feel. If you just want to simply change the color or size of certain element, you could override the styles of the predefined css classes. **CSS Customization** section lists some important css classes. If you need to change the layout of certain element, you could refer to the **Template Customization** part. - +## CSS Customization - onRangeChanged = (ev: { startTime: Date, endTime: Date }) => { - Events.query(ev, (events) => { - this.eventSource = events; - }); - }; +* monthview-primary-with-event +The date that is in current month and having events -* onEventSelected -The callback function triggered when an event is clicked +* monthview-secondary-with-event +The date that is in previous/next month and having events - +* monthview-selected +The selected date - onEventSelected = (event) => { - console.log(event.title); - }; +* monthview-current +The current date -* onTimeSelected -The callback function triggered when a date is selected in the monthview. -The ev parameter contains two fields, selectedTime and events, if there's no event at the selected time, the events field will be either undefined or empty array +* monthview-disabled +The disabled date - +* weekview-with-event +The date having all day events, applied to the day header in week view - onTimeSelected = (ev: { selectedTime: Date, events: any[] }) => { - console.log('Selected time: ' + ev.selectedTime + ', hasEvents: ' + (ev.events !== undefined && ev.events.length !== 0)); - }; +* week-view-current +The current date, applied to the day header in week view -* onTitleChanged -The callback function triggered when the view title is changed +* weekview-selected +The selected date, applied to the day header in week view - + {{view.dates[row*7+col].label}} + - - - - + +``` * monthviewInactiveDisplayEventTemplate Type: TemplateRef\ The template provides customized view for event displayed in the inactive monthview +``` html + + {{view.dates[row*7+col].label}} + - - - - + +``` * monthviewEventDetailTemplate Type: TemplateRef\ The template provides customized view for event detail section in the monthview +``` html + + ... + - - - - + +``` +* weekviewHeaderTemplate (version >= 0.4.5) +Type: TemplateRef\ +The template provides customized view for day header in the weekview +``` html + +
{{ viewDate.dayHeader }}
+
+ + +``` * weekviewAllDayEventTemplate Type: TemplateRef\ The template provides customized view for all day event in the weekview +``` html + +
{{displayEvent.event.title}}
+
- - - - + +``` * weekviewNormalEventTemplate Type: TemplateRef\ The template provides customized view for normal event in the weekview - +``` html + +
{{displayEvent.event.title}}
+
- + +``` -* dayviewAllDayEventTemplate +* dayviewAllDayEventTemplate Type: TemplateRef\ The template provides customized view for all day event in the dayview +``` html + +
{{displayEvent.event.title}}
+
- - - + +``` -* dayviewNormalEventTemplate +* dayviewNormalEventTemplate Type: TemplateRef\ The template provides customized view for normal event in the dayview - +``` html + +
{{displayEvent.event.title}}
+
+ + +``` + +* weekviewAllDayEventSectionTemplate (version >= 0.3) +Type: TemplateRef\ +The template provides customized view for all day event section (table part) in the weekview + +``` html + +
+
+ + +
+
+
+ +         +``` - +* weekviewNormalEventSectionTemplate (version >= 0.3) +Type: TemplateRef\ +The template provides customized view for normal event section (table part) in the weekview + +``` html + +
+
+ + +
+
+
+ +         +``` +* dayviewAllDayEventSectionTemplate (version >= 0.3) +Type: TemplateRef\ +The template provides customized view for all day event section (table part) in the dayview + +``` html + +
+ + +
+
+ +         +``` + +* dayviewNormalEventSectionTemplate (version >= 0.3) +Type: TemplateRef\ +The template provides customized view for normal event section (table part) in the dayview + +``` html + +
+
+ + +
+
+
+ +         +``` # EventSource @@ -272,31 +565,91 @@ EventSource is an array of event object which contains at least below fields: If allDay is set to true, the startTime has to be as a UTC date which time is set to 0:00 AM, because in an allDay event, only the date is considered, the exact time or timezone doesn't matter. For example, if an allDay event starting from 2014-05-09, then startTime is - var startTime = new Date(Date.UTC(2014, 4, 8)); +``` javascript + var startTime = new Date(Date.UTC(2014, 4, 8)); +``` -* endTime +* endTime     If allDay is set to true, the startTime has to be as a UTC date which time is set to 0:00 AM, because in an allDay event, only the date is considered, the exact time or timezone doesn't matter. For example, if an allDay event ending to 2014-05-10, then endTime is +``` javascript + var endTime = new Date(Date.UTC(2014, 4, 9)); +``` +* allDay     +Indicates the event is allDay event or regular event - var endTime = new Date(Date.UTC(2014, 4, 9)); +**Note** The calendar only watches for the eventSource reference for performance consideration. That means only you manually reassign the eventSource value, the calendar gets notified, and this is usually fit to the scenario when the range is changed, you load a new data set from the backend. In case you want to manually insert/remove/update the element in the eventSource array, you can call instance method ‘loadEvents’ event to notify the calendar manually. -* allDay -Indicates the event is allDay event or regular event +# Instance Methods +* loadEvents +When this method is called, the calendar will be forced to reload the events in the eventSource array. This is only necessary when you directly modify the element in the eventSource array. -# Localization -The DatePipe relies on LOCALE_ID to achieve localization. By default, the LOCALE_ID is **en-US**. You can override it in the module as below. If you pass **undefined**, the LOCALE_ID will be detected using the browser language setting. But using explicit value is recommended, as browser has different level of localization support. +``` typescript +import { CalendarComponent } from "ionic2-calendar/calendar"; +@Component({ + selector: 'page-home', + templateUrl: 'home.html' +}) +export class HomePage { + @ViewChild(CalendarComponent) myCalendar:CalendarComponent; + eventSource; + … + loadEvents: function() { + this.eventSource.push({ + title: 'test', + startTime: startTime, + endTime: endTime, + allDay: false + }); + this.myCalendar.loadEvents(); + } +} ``` + +# Localization +You could use *locale* option to achieve the localization. +If locale option is not specified, the calendar will use the LOCALE_ID set at the module level. +By default, the LOCALE_ID is **en-US**. You can override it in the module as below. If you pass **undefined**, the LOCALE_ID will be detected using the browser language setting. But using explicit value is recommended, as browser has different level of localization support. +Note that the event detail section in the month view doesn't support *locale* option, only LOCALE_ID takes effect. This is because it uses DatePipe in html directly. You could easily leverage customized event detail template to switch to other locale. + +``` typescript import { NgModule, LOCALE_ID } from '@angular/core'; @NgModule({ … providers: [ - { provide: LOCALE_ID, useValue: ‘zh-CN’ } + { provide: LOCALE_ID, useValue: 'zh-CN' } ] }) ``` +For version 0.4.x which depends on Ionic 3.9.2 and Angular 5.0, locale module needs to be registered explicitly in module file as below. +``` typescript +import { registerLocaleData } from '@angular/common'; +import localeZh from '@angular/common/locales/zh'; +registerLocaleData(localeZh); + +``` + +If you want to change the locale dynamically, you should use *locale* option instead of LOCALE_ID. + +# Performance Tuning +In the CPU profile, the default Intl based localization code occupies a big portion of the execution time. If you don’t need localization on certain parts, you can use the custom dateFormatter to override the date transform method. For example, the date in month view usually doesn’t require localization, you could use below code to just display the date part. If the month view day header doesn’t need to include the date, you could also use a string array containing static labels to save the date calculation. + +``` html + +``` +``` typescript +calendar = { + dateFormatter: { + formatMonthViewDay: function(date:Date) { + return date.getDate().toString(); + } + } +}; +``` + # Known issue This component updates the ion-slide dynamically so that only 3 looped slides are needed. The ion-slide in Ionic2 uses Swiper. It seems in the Swiper implementation, the next slide after the end of looped slide is a separate cached slide, instead of the first slide. @@ -311,4 +664,11 @@ Answer: This calendar has dependency on 'Intl'. Run *npm install intl@1.2.5* to Answer: If you bind currentDate like this: [currentDate]="calendar.currentDate". You need to assign calendar.currentDate a valid Date object * How to switch the calendar to previous/next month programmatically? -Answer: You can change currentDate to the date in previous/next month. +Answer: You can change currentDate to the date in previous/next month. You could also retrieve the Swiper element and then call the Swiper API directly. +``` +var mySwiper = document.querySelector('.swiper-container')['swiper']; + mySwiper.slideNext(); +``` + +* Error: Cannot read property 'dayHeaders' of undefined +Answer: Take a look at the Localization section. For version 0.4.x, you need to manually register the locale. \ No newline at end of file diff --git a/demo/config.js b/demo/config.js index f38ef5ea..acf55906 100644 --- a/demo/config.js +++ b/demo/config.js @@ -1,13 +1,13 @@ (function (global) { - var ngVer = '@2.2.1'; // lock in the angular package version; do not let it float to current! + var ngVer = '@2.4.8'; // lock in the angular package version; do not let it float to current! //map tells the System loader where to look for things var map = { 'app': 'app', // 'dist', - 'rxjs': 'https://unpkg.com/rxjs@5.0.0-beta.12', + 'rxjs': 'https://unpkg.com/rxjs@5.0.1', 'angular2-in-memory-web-api': 'https://unpkg.com/angular2-in-memory-web-api', // get latest 'ionic2-calendar': '../src', - 'ionic-angular': 'https://unpkg.com/ionic-angular@2.1.0', + 'ionic-angular': 'https://unpkg.com/ionic-angular@2.3.0', 'pages': 'pages', 'intl': 'https://unpkg.com/intl@1.2.5' }; diff --git a/demo/index.html b/demo/index.html index aeb8ae51..668d55e2 100644 --- a/demo/index.html +++ b/demo/index.html @@ -14,7 +14,7 @@ - + diff --git a/demo/pages/home.ts b/demo/pages/home.ts index ad28e3aa..32426063 100644 --- a/demo/pages/home.ts +++ b/demo/pages/home.ts @@ -11,7 +11,33 @@ export class HomePage { isToday:boolean; calendar = { mode: 'month', - currentDate: new Date() + currentDate: new Date(), + dateFormatter: { + formatMonthViewDay: function(date:Date) { + return date.getDate().toString(); + }, + formatMonthViewDayHeader: function(date:Date) { + return 'MonMH'; + }, + formatMonthViewTitle: function(date:Date) { + return 'testMT'; + }, + formatWeekViewDayHeader: function(date:Date) { + return 'MonWH'; + }, + formatWeekViewTitle: function(date:Date) { + return 'testWT'; + }, + formatWeekViewHourColumn: function(date:Date) { + return 'testWH'; + }, + formatDayViewHourColumn: function(date:Date) { + return 'testDH'; + }, + formatDayViewTitle: function(date:Date) { + return 'testDT'; + } + } }; constructor(private navController:NavController) { diff --git a/package.json b/package.json index 878e14fa..271f18bd 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "ionic2-calendar", - "version": "0.2.5", + "version": "0.4.6", "description": "Ionic2 calendar component", "keywords": [ "Ionic2", "calendar" ], "author": { - "name": "twinsbc" + "name": "twinssbc" }, "license": "MIT", "repository": { @@ -15,31 +15,27 @@ "url": "git+https://github.com/twinssbc/Ionic2-Calendar.git" }, "scripts": { - "build": "rm -rf dist && tsc && npm run copy_package", - "build-prod": "rm -rf aot && node_modules/.bin/ngc -p tsconfig-aot.json && node_modules/.bin/ngc src && mv src/*ngfactory.ts aot && rm -r aot/waste", + "build": "rm -rf dist && tsc && cp package.json dist/package.json", + "build-prod": "rm -rf aot && node_modules/.bin/ngc -p tsconfig-aot.json", "copy_static_files": "cp -r package.json README.md LICENSE tsconfig.json typings.json typings dist/", - "copy_static_files_prod": "cp -r package.json README.md LICENSE tsconfig.json typings.json typings aot/", - "dev": "tsc --watch", - "postinstall": "typings install" + "copy_static_files_prod": "cp -r package.json README.md LICENSE tsconfig.json aot/", + "dev": "tsc --watch" }, "main": "./index.js", "dependencies": {}, "devDependencies": { - "tslint-ionic-rules": "0.0.5", - "typescript": "2.0.3", - "typings": "1.3.1", - "@angular/common": "2.2.1", - "@angular/compiler": "2.2.1", - "@angular/compiler-cli": "2.2.1", - "@angular/core": "2.2.1", - "@angular/forms": "2.2.1", - "@angular/http": "2.2.1", - "@angular/platform-browser": "2.2.1", - "@angular/platform-browser-dynamic": "2.2.1", - "@angular/platform-server": "2.2.1", - "ionic-angular": "2.1.0", - "rxjs": "5.0.0-beta.12", - "zone.js": "0.6.26", + "typescript": "~2.4.2", + "@angular/common": "5.0.0", + "@angular/compiler": "5.0.0", + "@angular/compiler-cli": "5.0.0", + "@angular/core": "5.0.0", + "@angular/forms": "5.0.0", + "@angular/http": "5.0.0", + "@angular/platform-browser": "5.0.0", + "@angular/platform-browser-dynamic": "5.0.0", + "ionic-angular": "3.9.2", + "rxjs": "5.5.2", + "zone.js": "^0.8.18", "intl": "^1.2.5" } } diff --git a/src/calendar.module.ts b/src/calendar.module.ts index c79d8163..9f86099c 100644 --- a/src/calendar.module.ts +++ b/src/calendar.module.ts @@ -7,10 +7,11 @@ import { WeekViewComponent } from './weekview'; import { DayViewComponent } from './dayview'; import {CalendarComponent} from './calendar'; import { CalendarService } from './calendar.service'; +import { initPositionScrollComponent } from './init-position-scroll'; @NgModule({ declarations: [ - MonthViewComponent, WeekViewComponent, DayViewComponent, CalendarComponent + MonthViewComponent, WeekViewComponent, DayViewComponent, CalendarComponent, initPositionScrollComponent ], imports: [IonicModule], exports: [CalendarComponent], diff --git a/src/calendar.service.ts b/src/calendar.service.ts index 7be3e21a..17697f50 100644 --- a/src/calendar.service.ts +++ b/src/calendar.service.ts @@ -9,14 +9,17 @@ export class CalendarService { queryMode: QueryMode; currentDateChangedFromParent$: Observable; currentDateChangedFromChildren$: Observable; + eventSourceChanged$: Observable; private _currentDate: Date; private currentDateChangedFromParent = new Subject(); private currentDateChangedFromChildren = new Subject(); + private eventSourceChanged = new Subject(); constructor() { this.currentDateChangedFromParent$ = this.currentDateChangedFromParent.asObservable(); this.currentDateChangedFromChildren$ = this.currentDateChangedFromChildren.asObservable(); + this.eventSourceChanged$ = this.eventSourceChanged.asObservable(); } setCurrentDate(val: Date, fromParent: boolean = false) { @@ -86,7 +89,7 @@ export class CalendarService { getAdjacentViewStartTime(component: ICalendarComponent, direction: number): Date { let adjacentCalendarDate = this.getAdjacentCalendarDate(component.mode, direction); return component.getRange(adjacentCalendarDate).startTime; - }; + } populateAdjacentViews(component: ICalendarComponent) { let currentViewStartDate: Date, @@ -124,4 +127,8 @@ export class CalendarService { } } } + + loadEvents() { + this.eventSourceChanged.next(); + } } diff --git a/src/calendar.ts b/src/calendar.ts index 382094cd..e4423c4e 100644 --- a/src/calendar.ts +++ b/src/calendar.ts @@ -47,12 +47,15 @@ export interface IMonthViewRow { export interface IWeekView extends IView { dates: IWeekViewDateRow[]; rows: IWeekViewRow[][]; - dayHeaders: string[]; } export interface IWeekViewDateRow { + current?: boolean; date: Date; events: IDisplayEvent[]; + hasEvent?: boolean; + selected?: boolean; + dayHeader: string; } export interface IWeekViewRow { @@ -70,6 +73,10 @@ export interface IDisplayEvent { position?: number; } +export interface IDisplayWeekViewHeader { + viewDate: IWeekViewDateRow; +} + export interface IDisplayAllDayEvent { event: IEvent; } @@ -94,14 +101,45 @@ export interface ITimeSelected { } export interface IMonthViewDisplayEventTemplateContext { - view: IView, - row: number, - col: number + view: IView; + row: number; + col: number; } export interface IMonthViewEventDetailTemplateContext { - selectedDate: ITimeSelected, - noEventsLabel: string + selectedDate: ITimeSelected; + noEventsLabel: string; +} + +export interface IWeekViewAllDayEventSectionTemplateContext { + day: IWeekViewDateRow, + eventTemplate: TemplateRef +} + +export interface IWeekViewNormalEventSectionTemplateContext { + tm: IWeekViewRow, + eventTemplate: TemplateRef +} + +export interface IDayViewAllDayEventSectionTemplateContext { + alldayEvents: IDisplayAllDayEvent[], + eventTemplate: TemplateRef +} + +export interface IDayViewNormalEventSectionTemplateContext { + tm: IDayViewRow, + eventTemplate: TemplateRef +} + +export interface IDateFormatter { + formatMonthViewDay?: { (date:Date): string; }; + formatMonthViewDayHeader?: { (date:Date): string; }; + formatMonthViewTitle?: { (date:Date): string; }; + formatWeekViewDayHeader?: { (date:Date): string; }; + formatWeekViewTitle?: { (date:Date): string; }; + formatWeekViewHourColumn?: { (date:Date): string; }; + formatDayViewTitle?: { (date:Date): string; }; + formatDayViewHourColumn?: { (date:Date): string; }; } export type CalendarMode = 'day' | 'month' | 'week'; @@ -117,10 +155,10 @@ export enum Step { @Component({ selector: 'calendar', template: ` - - - - + + +
+
+ + +
+
+
+ +
+ + +
+
+ +
+
+ + +
+
+
boolean; @Input() monthviewDisplayEventTemplate:TemplateRef; @Input() monthviewInactiveDisplayEventTemplate:TemplateRef; @Input() monthviewEventDetailTemplate:TemplateRef; + @Input() weekviewHeaderTemplate:TemplateRef; @Input() weekviewAllDayEventTemplate:TemplateRef; @Input() weekviewNormalEventTemplate:TemplateRef; @Input() dayviewAllDayEventTemplate:TemplateRef; @Input() dayviewNormalEventTemplate:TemplateRef; + @Input() weekviewAllDayEventSectionTemplate:TemplateRef; + @Input() weekviewNormalEventSectionTemplate:TemplateRef; + @Input() dayviewAllDayEventSectionTemplate:TemplateRef; + @Input() dayviewNormalEventSectionTemplate:TemplateRef; + @Input() dateFormatter:IDateFormatter; + @Input() dir:string = ""; + @Input() scrollToHour:number = 0; + @Input() preserveScrollPosition:boolean = false; + @Input() lockSwipeToPrev:boolean = false; + @Input() lockSwipes:boolean = false; + @Input() locale:string = ""; + @Input() startHour:number = 0; + @Input() endHour:number = 24; + @Input() spaceBetween:number = 0; @Output() onCurrentDateChanged = new EventEmitter(); @Output() onRangeChanged = new EventEmitter(); @@ -288,9 +409,11 @@ export class CalendarComponent implements OnInit { private _currentDate:Date; private hourParts = 1; + private hourSegments = 1; private currentDateChangedFromChildrenSubscription:Subscription; - constructor(private calendarService:CalendarService, @Inject(LOCALE_ID) private locale:string) { + constructor(private calendarService:CalendarService, @Inject(LOCALE_ID) private appLocale:string) { + this.locale = appLocale; } ngOnInit() { @@ -301,7 +424,15 @@ export class CalendarComponent implements OnInit { this.autoSelect = true; } } + this.hourSegments = 60 / this.timeInterval; this.hourParts = 60 / this.step; + if(this.hourParts <= this.hourSegments) { + this.hourParts = 1; + } else { + this.hourParts = this.hourParts / this.hourSegments; + } + this.startHour = parseInt(this.startHour.toString()); + this.endHour = parseInt(this.endHour.toString()); this.calendarService.queryMode = this.queryMode; this.currentDateChangedFromChildrenSubscription = this.calendarService.currentDateChangedFromChildren$.subscribe(currentDate => { @@ -332,4 +463,8 @@ export class CalendarComponent implements OnInit { titleChanged(title:string) { this.onTitleChanged.emit(title); } + + loadEvents() { + this.calendarService.loadEvents(); + } } diff --git a/src/dayview.ts b/src/dayview.ts index 361d57c8..650de6d2 100644 --- a/src/dayview.ts +++ b/src/dayview.ts @@ -1,16 +1,16 @@ import { DatePipe } from '@angular/common'; import { Slides } from 'ionic-angular'; -import { Component, OnInit, OnChanges, HostBinding, Input, Output, EventEmitter, SimpleChanges, ViewChild, ViewEncapsulation, TemplateRef } from '@angular/core'; +import { Component, OnInit, OnChanges, HostBinding, Input, Output, EventEmitter, SimpleChanges, ViewChild, ViewEncapsulation, TemplateRef, ElementRef } from '@angular/core'; import { Subscription } from 'rxjs/Subscription'; -import { ICalendarComponent, IDayView, IDayViewRow, IDisplayEvent, IEvent, ITimeSelected, IRange, CalendarMode } from './calendar'; +import { ICalendarComponent, IDayView, IDayViewRow, IDisplayEvent, IEvent, ITimeSelected, IRange, CalendarMode, IDateFormatter } from './calendar'; import { CalendarService } from './calendar.service'; -import { IDisplayAllDayEvent } from "./calendar"; +import { IDisplayAllDayEvent, IDayViewAllDayEventSectionTemplateContext, IDayViewNormalEventSectionTemplateContext } from "./calendar"; @Component({ selector: 'dayview', template: ` - +
{{allDayLabel}}
@@ -21,14 +21,9 @@ import { IDisplayAllDayEvent } from "./calendar"; -
- -
+ + @@ -37,41 +32,35 @@ import { IDisplayAllDayEvent } from "./calendar";
- - + +
- +
- {{tm.time | date: formatHourColumn}} + {{hourColumnLabels[i]}} -
-
- -
-
+ +
- + + +
- +
- {{tm.time | date: formatHourColumn}} + {{hourColumnLabels[i]}}
-
+
@@ -83,14 +72,9 @@ import { IDisplayAllDayEvent } from "./calendar"; -
- -
+ + @@ -99,41 +83,35 @@ import { IDisplayAllDayEvent } from "./calendar";
- - + +
- +
- {{tm.time | date: formatHourColumn}} + {{hourColumnLabels[i]}} -
-
- -
-
+ +
- + + +
- +
- {{tm.time | date: formatHourColumn}} + {{hourColumnLabels[i]}}
-
+
@@ -145,14 +123,9 @@ import { IDisplayAllDayEvent } from "./calendar"; -
- -
+ + @@ -161,41 +134,35 @@ import { IDisplayAllDayEvent } from "./calendar";
- - + +
- +
- {{tm.time | date: formatHourColumn}} + {{hourColumnLabels[i]}} -
-
- -
-
+ +
- + + +
- +
- {{tm.time | date: formatHourColumn}} + {{hourColumnLabels[i]}}
-
+
`, @@ -276,6 +243,12 @@ import { IDisplayAllDayEvent } from "./calendar"; line-height: 50px; text-align: center; width: 50px; + border-left: 1px solid #ddd; + } + + [dir="rtl"] .dayview-allday-label { + border-right: 1px solid #ddd; + float: right; } .dayview-allday-content-wrapper { @@ -284,6 +257,11 @@ import { IDisplayAllDayEvent } from "./calendar"; height: 51px; } + [dir="rtl"] .dayview-allday-content-wrapper { + margin-left: 0; + margin-right: 50px; + } + .dayview-allday-content-table { min-height: 50px; } @@ -354,6 +332,11 @@ import { IDisplayAllDayEvent } from "./calendar"; .dayview-allday-content-wrapper { margin-left: 31px; } + + [dir="rtl"] .dayview-allday-content-wrapper { + margin-left: 0; + margin-right: 31px; + } } `], encapsulation: ViewEncapsulation.None @@ -364,6 +347,8 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { @Input() dayviewAllDayEventTemplate:TemplateRef; @Input() dayviewNormalEventTemplate:TemplateRef; + @Input() dayviewAllDayEventSectionTemplate:TemplateRef; + @Input() dayviewNormalEventSectionTemplate:TemplateRef; @Input() formatHourColumn:string; @Input() formatDayTitle:string; @@ -372,6 +357,16 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { @Input() eventSource:IEvent[]; @Input() markDisabled:(date:Date) => boolean; @Input() locale:string; + @Input() dateFormatter:IDateFormatter; + @Input() dir:string = ""; + @Input() scrollToHour:number = 0; + @Input() preserveScrollPosition:boolean; + @Input() lockSwipeToPrev:boolean; + @Input() lockSwipes:boolean; + @Input() startHour:number; + @Input() endHour:number; + @Input() spaceBetween:number; + @Input() hourSegments:number; @Output() onRangeChanged = new EventEmitter(); @Output() onEventSelected = new EventEmitter(); @@ -391,22 +386,69 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { private inited = false; private callbackOnInit = true; private currentDateChangedFromParentSubscription:Subscription; - - constructor(private calendarService:CalendarService) { + private eventSourceChangedSubscription:Subscription; + private hourColumnLabels:string[]; + private initScrollPosition:number; + private formatTitle:(date:Date) => string; + private formatHourColumnLabel:(date:Date) => string; + private hourRange:number; + + constructor(private calendarService:CalendarService, private elm:ElementRef) { } ngOnInit() { + this.hourRange = (this.endHour - this.startHour) * this.hourSegments; + if (this.dateFormatter && this.dateFormatter.formatDayViewTitle) { + this.formatTitle = this.dateFormatter.formatDayViewTitle; + } else { + let datePipe = new DatePipe(this.locale); + this.formatTitle = function (date:Date) { + return datePipe.transform(date, this.formatDayTitle); + }; + } + + if (this.dateFormatter && this.dateFormatter.formatDayViewHourColumn) { + this.formatHourColumnLabel = this.dateFormatter.formatDayViewHourColumn; + } else { + let datePipe = new DatePipe(this.locale); + this.formatHourColumnLabel = function (date:Date) { + return datePipe.transform(date, this.formatHourColumn); + }; + } + + if (this.lockSwipeToPrev) { + this.slider.lockSwipeToPrev(true); + } + + if (this.lockSwipes) { + this.slider.lockSwipes(true); + } + this.refreshView(); + this.hourColumnLabels = this.getHourColumnLabels(); + this.inited = true; this.currentDateChangedFromParentSubscription = this.calendarService.currentDateChangedFromParent$.subscribe(currentDate => { this.refreshView(); }); + + this.eventSourceChangedSubscription = this.calendarService.eventSourceChanged$.subscribe(() => { + this.onDataLoaded(); + }); } ngAfterViewInit() { let title = this.getTitle(); this.onTitleChanged.emit(title); + + if (this.scrollToHour > 0) { + let hourColumns = this.elm.nativeElement.querySelector('.dayview-normal-event-container').querySelectorAll('.calendar-hour-column'); + let me = this; + setTimeout(function () { + me.initScrollPosition = hourColumns[me.scrollToHour - me.startHour].offsetTop; + }, 0); + } } ngOnChanges(changes:SimpleChanges) { @@ -416,17 +458,32 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { if (eventSourceChange && eventSourceChange.currentValue) { this.onDataLoaded(); } + + let lockSwipeToPrev = changes['lockSwipeToPrev']; + if (lockSwipeToPrev) { + this.slider.lockSwipeToPrev(lockSwipeToPrev.currentValue); + } + + let lockSwipes = changes['lockSwipes']; + if (lockSwipes) { + this.slider.lockSwipes(lockSwipes.currentValue); + } } ngOnDestroy() { - if(this.currentDateChangedFromParentSubscription) { + if (this.currentDateChangedFromParentSubscription) { this.currentDateChangedFromParentSubscription.unsubscribe(); this.currentDateChangedFromParentSubscription = null; } + + if (this.eventSourceChangedSubscription) { + this.eventSourceChangedSubscription.unsubscribe(); + this.eventSourceChangedSubscription = null; + } } onSlideChanged() { - if(this.callbackOnInit) { + if (this.callbackOnInit) { this.callbackOnInit = false; return; } @@ -461,27 +518,47 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { this.direction = 0; } - static createDateObjects(startTime:Date):IDayViewRow[] { + static createDateObjects(startTime:Date, startHour: number, endHour: number, timeInterval: number):IDayViewRow[] { let rows:IDayViewRow[] = [], time:Date, currentHour = startTime.getHours(), - currentDate = startTime.getDate(); - - for (let hour = 0; hour < 24; hour += 1) { - time = new Date(startTime.getTime()); - time.setHours(currentHour + hour); - time.setDate(currentDate); - rows.push({ - time: time, - events: [] - }); + currentDate = startTime.getDate(), + hourStep, + minStep; + + if(timeInterval < 1) { + hourStep = Math.floor(1 / timeInterval); + minStep = 60; + } else { + hourStep = 1; + minStep = Math.floor(60 / timeInterval); + } + + for (let hour = startHour; hour < endHour; hour += hourStep) { + for(let interval = 0; interval < 60; interval += minStep ) { + time = new Date(startTime.getTime()); + time.setHours(currentHour + hour, interval); + time.setDate(currentDate); + rows.push({ + time: time, + events: [] + }); + } } return rows; } + private getHourColumnLabels():string[] { + let hourColumnLabels:string[] = []; + for (let hour = 0, length = this.views[0].rows.length; hour < length; hour += 1) { + hourColumnLabels.push(this.formatHourColumnLabel(this.views[0].rows[hour].time)); + } + return hourColumnLabels; + } + getViewData(startTime:Date):IDayView { return { - rows: DayViewComponent.createDateObjects(startTime), + rows: DayViewComponent.createDateObjects(startTime, this.startHour, this.endHour, this.hourSegments), allDayEvents: [] }; } @@ -508,12 +585,14 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { utcEndTime = new Date(Date.UTC(endTime.getFullYear(), endTime.getMonth(), endTime.getDate())), currentViewIndex = this.currentViewIndex, rows = this.views[currentViewIndex].rows, - allDayEvents = this.views[currentViewIndex].allDayEvents, + allDayEvents:IDisplayAllDayEvent[] = this.views[currentViewIndex].allDayEvents = [], oneHour = 3600000, eps = 0.016, - normalEventInRange = false; + normalEventInRange = false, + rangeStartRowIndex = this.startHour * this.hourSegments, + rangeEndRowIndex = this.endHour * this.hourSegments; - for (let hour = 0; hour < 24; hour += 1) { + for (let hour = 0; hour < this.hourRange; hour += 1) { rows[hour].events = []; } @@ -537,22 +616,22 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { normalEventInRange = true; } - let timeDiff:number; - let timeDifferenceStart:number; + let timeDiff: number; + let timeDifferenceStart: number; if (eventStartTime <= startTime) { timeDifferenceStart = 0; } else { timeDiff = eventStartTime.getTime() - startTime.getTime() - (eventStartTime.getTimezoneOffset() - startTime.getTimezoneOffset()) * 60000; - timeDifferenceStart = timeDiff / oneHour; + timeDifferenceStart = timeDiff / oneHour * this.hourSegments; } - let timeDifferenceEnd:number; + let timeDifferenceEnd: number; if (eventEndTime >= endTime) { timeDiff = endTime.getTime() - startTime.getTime() - (endTime.getTimezoneOffset() - startTime.getTimezoneOffset()) * 60000; - timeDifferenceEnd = timeDiff / oneHour; + timeDifferenceEnd = timeDiff / oneHour * this.hourSegments; } else { timeDiff = eventEndTime.getTime() - startTime.getTime() - (eventEndTime.getTimezoneOffset() - startTime.getTimezoneOffset()) * 60000; - timeDifferenceEnd = timeDiff / oneHour; + timeDifferenceEnd = timeDiff / oneHour * this.hourSegments; } let startIndex = Math.floor(timeDifferenceStart); @@ -560,32 +639,52 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { let startOffset = 0; let endOffset = 0; if (this.hourParts !== 1) { - startOffset = Math.floor((timeDifferenceStart - startIndex) * this.hourParts); - endOffset = Math.floor((endIndex - timeDifferenceEnd) * this.hourParts); + if (startIndex < rangeStartRowIndex) { + startOffset = 0; + } else { + startOffset = Math.floor((timeDifferenceStart - startIndex) * this.hourParts); + } + if (endIndex > rangeEndRowIndex) { + endOffset = 0; + } else { + endOffset = Math.floor((endIndex - timeDifferenceEnd) * this.hourParts); + } } - let displayEvent = { - event: event, - startIndex: startIndex, - endIndex: endIndex, - startOffset: startOffset, - endOffset: endOffset - }; - - let eventSet = rows[startIndex].events; - if (eventSet) { - eventSet.push(displayEvent); + if (startIndex < rangeStartRowIndex) { + startIndex = 0; } else { - eventSet = []; - eventSet.push(displayEvent); - rows[startIndex].events = eventSet; + startIndex -= rangeStartRowIndex; + } + if (endIndex > rangeEndRowIndex) { + endIndex = rangeEndRowIndex; + } + endIndex -= rangeStartRowIndex; + + if (startIndex < endIndex) { + let displayEvent = { + event: event, + startIndex: startIndex, + endIndex: endIndex, + startOffset: startOffset, + endOffset: endOffset + }; + + let eventSet = rows[startIndex].events; + if (eventSet) { + eventSet.push(displayEvent); + } else { + eventSet = []; + eventSet.push(displayEvent); + rows[startIndex].events = eventSet; + } } } } if (normalEventInRange) { let orderedEvents:IDisplayEvent[] = []; - for (let hour = 0; hour < 24; hour += 1) { + for (let hour = 0; hour < this.hourRange; hour += 1) { if (rows[hour].events) { rows[hour].events.sort(DayViewComponent.compareEventByStartOffset); @@ -610,8 +709,9 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { } getTitle():string { - let startingDate = this.range.startTime; - return new DatePipe(this.locale).transform(startingDate, this.formatDayTitle); + let startingDate = new Date(this.range.startTime.getTime()); + startingDate.setHours(12, 0,0,0); + return this.formatTitle(startingDate); } private static compareEventByStartOffset(eventA:IDisplayEvent, eventB:IDisplayEvent) { @@ -619,7 +719,7 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { } select(selectedTime:Date, events:IDisplayEvent[]) { - var disabled = false; + let disabled = false; if (this.markDisabled) { disabled = this.markDisabled(selectedTime); } @@ -633,7 +733,7 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { placeEvents(orderedEvents:IDisplayEvent[]) { this.calculatePosition(orderedEvents); - DayViewComponent.calculateWidth(orderedEvents); + DayViewComponent.calculateWidth(orderedEvents, this.hourRange, this.hourParts); } placeAllDayEvents(orderedEvents:IDisplayEvent[]) { @@ -651,7 +751,7 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { if (earlyEvent.endIndex <= lateEvent.startIndex) { return false; } else { - return !(earlyEvent.endIndex - lateEvent.startIndex === 1 && earlyEvent.endOffset + lateEvent.startOffset > this.hourParts); + return !(earlyEvent.endIndex - lateEvent.startIndex === 1 && earlyEvent.endOffset + lateEvent.startOffset >= this.hourParts); } } @@ -681,16 +781,23 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { events[i].position = maxColumn++; } } + + if (this.dir === 'rtl') { + for (let i = 0; i < len; i += 1) { + events[i].position = maxColumn - 1 - events[i].position; + } + } } - private static calculateWidth(orderedEvents:IDisplayEvent[]) { - let cells:{ calculated: boolean; events: IDisplayEvent[]; }[] = new Array(24); + private static calculateWidth(orderedEvents:IDisplayEvent[], size:number, hourParts: number) { + let totalSize = size * hourParts, + cells:{ calculated: boolean; events: IDisplayEvent[]; }[] = new Array(totalSize); // sort by position in descending order, the right most columns should be calculated first orderedEvents.sort((eventA, eventB) => { return eventB.position - eventA.position; }); - for (let i = 0; i < 24; i += 1) { + for (let i = 0; i < totalSize; i += 1) { cells[i] = { calculated: false, events: [] @@ -699,8 +806,8 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { let len = orderedEvents.length; for (let i = 0; i < len; i += 1) { let event = orderedEvents[i]; - let index = event.startIndex; - while (index < event.endIndex) { + let index = event.startIndex * hourParts + event.startOffset; + while (index < event.endIndex * hourParts - event.endOffset) { cells[index].events.push(event); index += 1; } @@ -714,8 +821,8 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { event.overlapNumber = overlapNumber; let eventQueue = [event]; while ((event = eventQueue.shift())) { - let index = event.startIndex; - while (index < event.endIndex) { + let index = event.startIndex * hourParts + event.startOffset; + while (index < event.endIndex * hourParts - event.endOffset) { if (!cells[index].calculated) { cells[index].calculated = true; if (cells[index].events) { @@ -740,4 +847,8 @@ export class DayViewComponent implements ICalendarComponent, OnInit, OnChanges { eventSelected(event:IEvent) { this.onEventSelected.emit(event); } + + setScrollPosition(scrollPosition:number) { + this.initScrollPosition = scrollPosition; + } } diff --git a/src/init-position-scroll.ts b/src/init-position-scroll.ts new file mode 100644 index 00000000..9bd61b07 --- /dev/null +++ b/src/init-position-scroll.ts @@ -0,0 +1,55 @@ +import { Scroll } from 'ionic-angular'; +import { Component, Input, Output, EventEmitter, ElementRef, SimpleChanges } from '@angular/core'; + +@Component({ + selector: 'init-position-scroll', + template: ` + + + + ` +}) +export class initPositionScrollComponent extends Scroll { + @Input() initPosition:number; + @Input() emitEvent:boolean; + @Output() onScroll = new EventEmitter(); + + private element:ElementRef; + private scrollContent:HTMLElement; + private handler:()=>void; + private listenerAttached:boolean = false; + + constructor(el:ElementRef) { + super(); + this.element = el; + } + + ngOnChanges(changes:SimpleChanges) { + let initPosition = changes['initPosition']; + if (initPosition && initPosition.currentValue !== undefined && this.scrollContent) { + this.scrollContent.scrollTop = initPosition.currentValue; + } + } + + ngAfterViewInit() { + const scrollContent = this.scrollContent = this.element.nativeElement.querySelector('.scroll-content'); + if (this.initPosition !== undefined) { + scrollContent.scrollTop = this.initPosition; + } + + if (this.emitEvent && !this.listenerAttached) { + let onScroll = this.onScroll; + this.handler = function () { + onScroll.emit(scrollContent.scrollTop); + }; + this.listenerAttached = true; + scrollContent.addEventListener('scroll', this.handler); + } + } + + ngOnDestroy() { + if (this.listenerAttached) { + this.scrollContent.removeEventListener('scroll', this.handler); + } + } +} diff --git a/src/monthview.ts b/src/monthview.ts index 45f6c54f..01b8941f 100644 --- a/src/monthview.ts +++ b/src/monthview.ts @@ -3,7 +3,7 @@ import { Subscription } from 'rxjs/Subscription'; import { DatePipe } from '@angular/common'; import { Slides } from 'ionic-angular'; -import { ICalendarComponent, IEvent, IMonthView, IMonthViewRow, ITimeSelected, IRange, CalendarMode } from './calendar'; +import { ICalendarComponent, IEvent, IMonthView, IMonthViewRow, ITimeSelected, IRange, CalendarMode, IDateFormatter } from './calendar'; import { CalendarService } from './calendar.service'; import { IMonthViewDisplayEventTemplateContext } from "./calendar"; @@ -11,7 +11,7 @@ import { IMonthViewDisplayEventTemplateContext } from "./calendar"; selector: 'monthview', template: `
- + @@ -25,9 +25,9 @@ import { IMonthViewDisplayEventTemplateContext } from "./calendar"; @@ -43,9 +43,9 @@ import { IMonthViewDisplayEventTemplateContext } from "./calendar"; @@ -64,9 +64,9 @@ import { IMonthViewDisplayEventTemplateContext } from "./calendar"; @@ -82,9 +82,9 @@ import { IMonthViewDisplayEventTemplateContext } from "./calendar"; @@ -103,9 +103,9 @@ import { IMonthViewDisplayEventTemplateContext } from "./calendar"; @@ -121,18 +121,18 @@ import { IMonthViewDisplayEventTemplateContext } from "./calendar";
- + +
- + +
- + +
- + +
- + +
- + +
- + +
`, styles: [` @@ -242,6 +242,11 @@ export class MonthViewComponent implements ICalendarComponent, OnInit, OnChanges @Input() autoSelect:boolean = true; @Input() markDisabled:(date:Date) => boolean; @Input() locale:string; + @Input() dateFormatter:IDateFormatter; + @Input() dir:string = ""; + @Input() lockSwipeToPrev:boolean; + @Input() lockSwipes:boolean; + @Input() spaceBetween:number; @Output() onRangeChanged = new EventEmitter(); @Output() onEventSelected = new EventEmitter(); @@ -259,17 +264,60 @@ export class MonthViewComponent implements ICalendarComponent, OnInit, OnChanges private inited = false; private callbackOnInit = true; private currentDateChangedFromParentSubscription:Subscription; + private eventSourceChangedSubscription:Subscription; + private formatDayLabel:(date:Date) => string; + private formatDayHeaderLabel:(date:Date) => string; + private formatTitle:(date:Date) => string; constructor(private calendarService:CalendarService) { } ngOnInit() { + if (this.dateFormatter && this.dateFormatter.formatMonthViewDay) { + this.formatDayLabel = this.dateFormatter.formatMonthViewDay; + } else { + let dayLabelDatePipe = new DatePipe('en-US'); + this.formatDayLabel = function (date:Date) { + return dayLabelDatePipe.transform(date, this.formatDay); + }; + } + + if (this.dateFormatter && this.dateFormatter.formatMonthViewDayHeader) { + this.formatDayHeaderLabel = this.dateFormatter.formatMonthViewDayHeader; + } else { + let datePipe = new DatePipe(this.locale); + this.formatDayHeaderLabel = function (date:Date) { + return datePipe.transform(date, this.formatDayHeader); + }; + } + + if (this.dateFormatter && this.dateFormatter.formatMonthViewTitle) { + this.formatTitle = this.dateFormatter.formatMonthViewTitle; + } else { + let datePipe = new DatePipe(this.locale); + this.formatTitle = function (date:Date) { + return datePipe.transform(date, this.formatMonthTitle); + }; + } + + if (this.lockSwipeToPrev) { + this.slider.lockSwipeToPrev(true); + } + + if (this.lockSwipes) { + this.slider.lockSwipes(true); + } + this.refreshView(); this.inited = true; this.currentDateChangedFromParentSubscription = this.calendarService.currentDateChangedFromParent$.subscribe(currentDate => { this.refreshView(); }); + + this.eventSourceChangedSubscription = this.calendarService.eventSourceChanged$.subscribe(() => { + this.onDataLoaded(); + }); } ngOnDestroy() { @@ -277,6 +325,11 @@ export class MonthViewComponent implements ICalendarComponent, OnInit, OnChanges this.currentDateChangedFromParentSubscription.unsubscribe(); this.currentDateChangedFromParentSubscription = null; } + + if (this.eventSourceChangedSubscription) { + this.eventSourceChangedSubscription.unsubscribe(); + this.eventSourceChangedSubscription = null; + } } ngOnChanges(changes:SimpleChanges) { @@ -286,6 +339,16 @@ export class MonthViewComponent implements ICalendarComponent, OnInit, OnChanges if (eventSourceChange && eventSourceChange.currentValue) { this.onDataLoaded(); } + + let lockSwipeToPrev = changes['lockSwipeToPrev']; + if (lockSwipeToPrev) { + this.slider.lockSwipeToPrev(lockSwipeToPrev.currentValue); + } + + let lockSwipes = changes['lockSwipes']; + if (lockSwipes) { + this.slider.lockSwipes(lockSwipes.currentValue); + } } ngAfterViewInit() { @@ -332,7 +395,7 @@ export class MonthViewComponent implements ICalendarComponent, OnInit, OnChanges this.moveOnSelected = false; } - createDateObject(date:Date, format:string):IMonthViewRow { + createDateObject(date:Date):IMonthViewRow { var disabled = false; if (this.markDisabled) { disabled = this.markDisabled(date); @@ -341,7 +404,7 @@ export class MonthViewComponent implements ICalendarComponent, OnInit, OnChanges return { date: date, events: [], - label: new DatePipe('en-US').transform(date, format), + label: this.formatDayLabel(date), secondary: false, disabled: disabled }; @@ -367,14 +430,14 @@ export class MonthViewComponent implements ICalendarComponent, OnInit, OnChanges let dates = MonthViewComponent.getDates(startDate, 42); let days:IMonthViewRow[] = []; for (let i = 0; i < 42; i++) { - let dateObject = this.createDateObject(dates[i], this.formatDay); + let dateObject = this.createDateObject(dates[i]); dateObject.secondary = dates[i].getMonth() !== month; days[i] = dateObject; } let dayHeaders:string[] = []; for (let i = 0; i < 7; i++) { - dayHeaders.push(new DatePipe(this.locale).transform(days[i].date, this.formatDayHeader)); + dayHeaders.push(this.formatDayHeaderLabel(days[i].date)); } return { dates: days, @@ -572,8 +635,8 @@ export class MonthViewComponent implements ICalendarComponent, OnInit, OnChanges date = currentViewStartDate.getDate(), month = (currentViewStartDate.getMonth() + (date !== 1 ? 1 : 0)) % 12, year = currentViewStartDate.getFullYear() + (date !== 1 && month === 0 ? 1 : 0), - headerDate = new Date(year, month, 1); - return new DatePipe(this.locale).transform(headerDate, this.formatMonthTitle); + headerDate = new Date(year, month, 1, 12, 0, 0, 0); + return this.formatTitle(headerDate); } private compareEvent(event1:IEvent, event2:IEvent):number { diff --git a/src/weekview.ts b/src/weekview.ts index 1386537d..18c20bc5 100644 --- a/src/weekview.ts +++ b/src/weekview.ts @@ -1,22 +1,27 @@ import { DatePipe } from '@angular/common'; import { Slides } from 'ionic-angular'; -import { Component, OnInit, OnChanges, HostBinding, Input, Output, EventEmitter, SimpleChanges, ViewChild, ViewEncapsulation, TemplateRef } from '@angular/core'; +import { Component, OnInit, OnChanges, HostBinding, Input, Output, EventEmitter, SimpleChanges, ViewChild, ViewEncapsulation, TemplateRef, ElementRef } from '@angular/core'; import { Subscription } from 'rxjs/Subscription'; -import { ICalendarComponent, IDisplayEvent, IEvent, ITimeSelected, IRange, IWeekView, IWeekViewRow, IWeekViewDateRow, CalendarMode } from './calendar'; +import { ICalendarComponent, IDisplayEvent, IEvent, ITimeSelected, IRange, IWeekView, IWeekViewRow, IWeekViewDateRow, CalendarMode, IDateFormatter, IDisplayWeekViewHeader } from './calendar'; import { CalendarService } from './calendar.service'; -import { IDisplayAllDayEvent } from "./calendar"; +import { IDisplayAllDayEvent, IWeekViewAllDayEventSectionTemplateContext, IWeekViewNormalEventSectionTemplateContext } from "./calendar"; @Component({ selector: 'weekview', template: ` - + - @@ -29,44 +34,31 @@ import { IDisplayAllDayEvent } from "./calendar";
{{dayHeader}} + + +
-
-
- -
-
+ +
- + - +
- {{row[0].time | date: formatHourColumn}} + {{hourColumnLabels[i]}} -
-
- -
-
+ +
-
+
@@ -82,19 +74,19 @@ import { IDisplayAllDayEvent } from "./calendar";
- + - +
- {{row[0].time | date: formatHourColumn}} + {{hourColumnLabels[i]}}
-
+
@@ -102,7 +94,12 @@ import { IDisplayAllDayEvent } from "./calendar"; - {{dayHeader}} + + + @@ -115,44 +112,33 @@ import { IDisplayAllDayEvent } from "./calendar"; -
-
- -
-
+ + - + - +
- {{row[0].time | date: formatHourColumn}} + {{hourColumnLabels[i]}}
-
- -
+ +
-
+
@@ -168,19 +154,19 @@ import { IDisplayAllDayEvent } from "./calendar";
- + - +
- {{row[0].time | date: formatHourColumn}} + {{hourColumnLabels[i]}}
-
+
@@ -188,7 +174,12 @@ import { IDisplayAllDayEvent } from "./calendar"; - {{dayHeader}} + + + @@ -201,44 +192,33 @@ import { IDisplayAllDayEvent } from "./calendar"; -
-
- -
-
+ + - + - +
- {{row[0].time | date: formatHourColumn}} + {{hourColumnLabels[i]}}
-
- -
+ +
-
+
@@ -254,19 +234,19 @@ import { IDisplayAllDayEvent } from "./calendar";
- + - +
- {{row[0].time | date: formatHourColumn}} + {{hourColumnLabels[i]}}
-
+
@@ -348,6 +328,12 @@ import { IDisplayAllDayEvent } from "./calendar"; line-height: 50px; text-align: center; width: 50px; + border-left: 1px solid #ddd; + } + + [dir="rtl"] .weekview-allday-label { + float: right; + border-right: 1px solid #ddd; } .weekview-allday-content-wrapper { @@ -356,6 +342,11 @@ import { IDisplayAllDayEvent } from "./calendar"; height: 51px; } + [dir="rtl"] .weekview-allday-content-wrapper { + margin-left: 0; + margin-right: 50px; + } + .weekview-allday-content-table { min-height: 50px; } @@ -438,6 +429,11 @@ import { IDisplayAllDayEvent } from "./calendar"; .weekview-allday-content-wrapper { margin-left: 31px; } + + [dir="rtl"] .weekview-allday-content-wrapper { + margin-left: 0; + margin-right: 31px; + } } `], encapsulation: ViewEncapsulation.None @@ -446,8 +442,11 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges @ViewChild('weekSlider') slider:Slides; @HostBinding('class.weekview') class = true; + @Input() weekviewHeaderTemplate:TemplateRef; @Input() weekviewAllDayEventTemplate:TemplateRef; @Input() weekviewNormalEventTemplate:TemplateRef; + @Input() weekviewAllDayEventSectionTemplate:TemplateRef; + @Input() weekviewNormalEventSectionTemplate:TemplateRef; @Input() formatWeekTitle:string; @Input() formatWeekViewDayHeader:string; @@ -456,8 +455,19 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges @Input() allDayLabel:string; @Input() hourParts:number; @Input() eventSource:IEvent[]; + @Input() autoSelect:boolean = true; @Input() markDisabled:(date:Date) => boolean; @Input() locale:string; + @Input() dateFormatter:IDateFormatter; + @Input() dir:string = ""; + @Input() scrollToHour:number = 0; + @Input() preserveScrollPosition:boolean; + @Input() lockSwipeToPrev:boolean; + @Input() lockSwipes:boolean; + @Input() startHour:number; + @Input() endHour:number; + @Input() spaceBetween:number; + @Input() hourSegments:number; @Output() onRangeChanged = new EventEmitter(); @Output() onEventSelected = new EventEmitter(); @@ -473,22 +483,78 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges private inited = false; private callbackOnInit = true; private currentDateChangedFromParentSubscription:Subscription; - - constructor(private calendarService:CalendarService) { + private eventSourceChangedSubscription:Subscription; + private hourColumnLabels:string[]; + private initScrollPosition:number; + private formatDayHeader:(date:Date) => string; + private formatTitle:(date:Date) => string; + private formatHourColumnLabel:(date:Date) => string; + private hourRange:number; + + constructor(private calendarService:CalendarService, private elm:ElementRef) { } ngOnInit() { + this.hourRange = (this.endHour - this.startHour) * this.hourSegments; + if (this.dateFormatter && this.dateFormatter.formatWeekViewDayHeader) { + this.formatDayHeader = this.dateFormatter.formatWeekViewDayHeader; + } else { + let datePipe = new DatePipe(this.locale); + this.formatDayHeader = function (date:Date) { + return datePipe.transform(date, this.formatWeekViewDayHeader); + }; + } + + if (this.dateFormatter && this.dateFormatter.formatWeekViewTitle) { + this.formatTitle = this.dateFormatter.formatWeekViewTitle; + } else { + let datePipe = new DatePipe(this.locale); + this.formatTitle = function (date:Date) { + return datePipe.transform(date, this.formatWeekTitle); + }; + } + + if (this.dateFormatter && this.dateFormatter.formatWeekViewHourColumn) { + this.formatHourColumnLabel = this.dateFormatter.formatWeekViewHourColumn; + } else { + let datePipe = new DatePipe(this.locale); + this.formatHourColumnLabel = function (date:Date) { + return datePipe.transform(date, this.formatHourColumn); + }; + } + + if (this.lockSwipeToPrev) { + this.slider.lockSwipeToPrev(true); + } + + if (this.lockSwipes) { + this.slider.lockSwipes(true); + } + this.refreshView(); + this.hourColumnLabels = this.getHourColumnLabels(); this.inited = true; this.currentDateChangedFromParentSubscription = this.calendarService.currentDateChangedFromParent$.subscribe(currentDate => { this.refreshView(); }); + + this.eventSourceChangedSubscription = this.calendarService.eventSourceChanged$.subscribe(() => { + this.onDataLoaded(); + }); } ngAfterViewInit() { let title = this.getTitle(); this.onTitleChanged.emit(title); + + if (this.scrollToHour > 0) { + let hourColumns = this.elm.nativeElement.querySelector('.weekview-normal-event-container').querySelectorAll('.calendar-hour-column'); + let me = this; + setTimeout(function () { + me.initScrollPosition = hourColumns[me.scrollToHour - me.startHour].offsetTop; + }, 0); + } } ngOnChanges(changes:SimpleChanges) { @@ -498,17 +564,32 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges if (eventSourceChange && eventSourceChange.currentValue) { this.onDataLoaded(); } + + let lockSwipeToPrev = changes['lockSwipeToPrev']; + if (lockSwipeToPrev) { + this.slider.lockSwipeToPrev(lockSwipeToPrev.currentValue); + } + + let lockSwipes = changes['lockSwipes']; + if (lockSwipes) { + this.slider.lockSwipes(lockSwipes.currentValue); + } } ngOnDestroy() { - if(this.currentDateChangedFromParentSubscription) { + if (this.currentDateChangedFromParentSubscription) { this.currentDateChangedFromParentSubscription.unsubscribe(); this.currentDateChangedFromParentSubscription = null; } + + if (this.eventSourceChangedSubscription) { + this.eventSourceChangedSubscription.unsubscribe(); + this.eventSourceChangedSubscription = null; + } } onSlideChanged() { - if(this.callbackOnInit) { + if (this.callbackOnInit) { this.callbackOnInit = false; return; } @@ -544,23 +625,35 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges this.direction = 0; } - static createDateObjects(startTime:Date):IWeekViewRow[][] { + static createDateObjects(startTime:Date, startHour: number, endHour: number, timeInterval: number):IWeekViewRow[][] { let times:IWeekViewRow[][] = [], currentHour = startTime.getHours(), - currentDate = startTime.getDate(); + currentDate = startTime.getDate(), + hourStep, + minStep; - for (let hour = 0; hour < 24; hour += 1) { - let row:IWeekViewRow[] = []; - for (let day = 0; day < 7; day += 1) { - let time = new Date(startTime.getTime()); - time.setHours(currentHour + hour); - time.setDate(currentDate + day); - row.push({ - events: [], - time: time - }); + if(timeInterval < 1) { + hourStep = Math.floor(1 / timeInterval); + minStep = 60; + } else { + hourStep = 1; + minStep = Math.floor(60 / timeInterval); + } + + for (let hour = startHour; hour < endHour; hour += hourStep) { + for (let interval = 0; interval < 60; interval += minStep) { + let row:IWeekViewRow[] = []; + for (let day = 0; day < 7; day += 1) { + let time = new Date(startTime.getTime()); + time.setHours(currentHour + hour, interval); + time.setDate(currentDate + day); + row.push({ + events: [], + time: time + }); + } + times.push(row); } - times.push(row); } return times; } @@ -573,24 +666,31 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges while (i < n) { dates[i++] = { date: new Date(current.getTime()), - events: [] + events: [], + dayHeader: '' }; current.setDate(current.getDate() + 1); } return dates; } + private getHourColumnLabels():string[] { + let hourColumnLabels:string[] = []; + for (let hour = 0, length = this.views[0].rows.length; hour < length; hour += 1) { + hourColumnLabels.push(this.formatHourColumnLabel(this.views[0].rows[hour][0].time)); + } + return hourColumnLabels; + } + getViewData(startTime:Date):IWeekView { let dates = WeekViewComponent.getDates(startTime, 7); - let dayHeaders:string[] = []; for (let i = 0; i < 7; i++) { - dayHeaders.push(new DatePipe(this.locale).transform(dates[i].date, this.formatWeekViewDayHeader)); + dates[i].dayHeader = this.formatDayHeader(dates[i].date); } return { - rows: WeekViewComponent.createDateObjects(startTime), - dates: dates, - dayHeaders: dayHeaders + rows: WeekViewComponent.createDateObjects(startTime, this.startHour, this.endHour, this.hourSegments), + dates: dates }; } @@ -629,14 +729,19 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges // add allday eps eps = 0.016, allDayEventInRange = false, - normalEventInRange = false; + normalEventInRange = false, + rangeStartRowIndex = this.startHour * this.hourSegments, + rangeEndRowIndex = this.endHour * this.hourSegments, + allRows = 24 * this.hourSegments; + for (let i = 0; i < 7; i += 1) { dates[i].events = []; + dates[i].hasEvent = false; } for (let day = 0; day < 7; day += 1) { - for (let hour = 0; hour < 24; hour += 1) { + for (let hour = 0; hour < this.hourRange; hour += 1) { rows[hour][day].events = []; } } @@ -679,6 +784,7 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges eventSet.push(displayAllDayEvent); dates[allDayStartIndex].events = eventSet; } + dates[allDayStartIndex].hasEvent = true; } } else { if (eventEndTime <= startTime || eventStartTime >= endTime) { @@ -692,55 +798,80 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges timeDifferenceStart = 0; } else { timeDiff = eventStartTime.getTime() - startTime.getTime() - (eventStartTime.getTimezoneOffset() - startTime.getTimezoneOffset()) * 60000; - timeDifferenceStart = timeDiff / oneHour; + timeDifferenceStart = timeDiff / oneHour * this.hourSegments; } let timeDifferenceEnd:number; if (eventEndTime >= endTime) { timeDiff = endTime.getTime() - startTime.getTime() - (endTime.getTimezoneOffset() - startTime.getTimezoneOffset()) * 60000; - timeDifferenceEnd = timeDiff / oneHour; + timeDifferenceEnd = timeDiff / oneHour * this.hourSegments; } else { timeDiff = eventEndTime.getTime() - startTime.getTime() - (eventEndTime.getTimezoneOffset() - startTime.getTimezoneOffset()) * 60000; - timeDifferenceEnd = timeDiff / oneHour; + timeDifferenceEnd = timeDiff / oneHour * this.hourSegments; } let startIndex = Math.floor(timeDifferenceStart), endIndex = Math.ceil(timeDifferenceEnd - eps), - startRowIndex = startIndex % 24, - dayIndex = Math.floor(startIndex / 24), - endOfDay = dayIndex * 24, + startRowIndex = startIndex % allRows, + dayIndex = Math.floor(startIndex / allRows), + endOfDay = dayIndex * allRows, startOffset = 0, endOffset = 0; if (this.hourParts !== 1) { - startOffset = Math.floor((timeDifferenceStart - startIndex) * this.hourParts); + if (startRowIndex < rangeStartRowIndex) { + startOffset = 0; + } else { + startOffset = Math.floor((timeDifferenceStart - startIndex) * this.hourParts); + } } do { - endOfDay += 24; + endOfDay += allRows; let endRowIndex:number; - if (endOfDay <= endIndex) { - endRowIndex = 24; + if (endOfDay < endIndex) { + endRowIndex = allRows; } else { - endRowIndex = endIndex % 24; + if(endOfDay === endIndex) { + endRowIndex = allRows; + } else { + endRowIndex = endIndex % allRows; + } if (this.hourParts !== 1) { - endOffset = Math.floor((endIndex - timeDifferenceEnd) * this.hourParts); + if (endRowIndex > rangeEndRowIndex) { + endOffset = 0; + } else { + endOffset = Math.floor((endIndex - timeDifferenceEnd) * this.hourParts); + } } } - let displayEvent = { - event: event, - startIndex: startRowIndex, - endIndex: endRowIndex, - startOffset: startOffset, - endOffset: endOffset - }; - let eventSet = rows[startRowIndex][dayIndex].events; - if (eventSet) { - eventSet.push(displayEvent); + if(startRowIndex < rangeStartRowIndex) { + startRowIndex = 0; } else { - eventSet = []; - eventSet.push(displayEvent); - rows[startRowIndex][dayIndex].events = eventSet; + startRowIndex -= rangeStartRowIndex; + } + if(endRowIndex > rangeEndRowIndex) { + endRowIndex = rangeEndRowIndex; + } + endRowIndex -= rangeStartRowIndex; + + if(startRowIndex < endRowIndex) { + let displayEvent = { + event: event, + startIndex: startRowIndex, + endIndex: endRowIndex, + startOffset: startOffset, + endOffset: endOffset + }; + let eventSet = rows[startRowIndex][dayIndex].events; + if (eventSet) { + eventSet.push(displayEvent); + } else { + eventSet = []; + eventSet.push(displayEvent); + rows[startRowIndex][dayIndex].events = eventSet; + } + dates[dayIndex].hasEvent = true; } startRowIndex = 0; startOffset = 0; @@ -753,7 +884,7 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges if (normalEventInRange) { for (let day = 0; day < 7; day += 1) { let orderedEvents:IDisplayEvent[] = []; - for (let hour = 0; hour < 24; hour += 1) { + for (let hour = 0; hour < this.hourRange; hour += 1) { if (rows[hour][day].events) { rows[hour][day].events.sort(WeekViewComponent.compareEventByStartOffset); orderedEvents = orderedEvents.concat(rows[hour][day].events); @@ -776,6 +907,31 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges this.placeAllDayEvents(orderedAllDayEvents); } } + + if (this.autoSelect) { + let findSelected = false; + let selectedDate; + for (let r = 0; r < 7; r += 1) { + if (dates[r].selected) { + selectedDate = dates[r]; + findSelected = true; + break; + } + } + + if (findSelected) { + let disabled = false; + if (this.markDisabled) { + disabled = this.markDisabled(selectedDate.date); + } + + this.onTimeSelected.emit({ + selectedTime: selectedDate.date, + events: selectedDate.events.map(e => e.event), + disabled: disabled + }); + } + } } refreshView() { @@ -786,29 +942,41 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges this.onTitleChanged.emit(title); } this.calendarService.populateAdjacentViews(this); + this.updateCurrentView(this.range.startTime, this.views[this.currentViewIndex]); this.calendarService.rangeChanged(this); } getTitle():string { - let firstDayOfWeek = this.range.startTime, - weekFormat = '$n', - weekNumberIndex = this.formatWeekTitle.indexOf(weekFormat), - title = new DatePipe(this.locale).transform(firstDayOfWeek, this.formatWeekTitle); + let firstDayOfWeek = new Date(this.range.startTime.getTime()); + firstDayOfWeek.setHours(12, 0, 0, 0); + return this.formatTitle(firstDayOfWeek); + } + + getHighlightClass(date: IWeekViewDateRow):string { + let className = ''; + + if (date.hasEvent) { + if (className) { + className += ' '; + } + className = 'weekview-with-event'; + } - if (weekNumberIndex !== -1) { - let weekNumber = String(WeekViewComponent.getISO8601WeekNumber(firstDayOfWeek)); - title = title.replace(weekFormat, weekNumber); + if (date.selected) { + if (className) { + className += ' '; + } + className += 'weekview-selected'; } - return title; - } + if (date.current) { + if (className) { + className += ' '; + } + className += 'weekview-current'; + } - private static getISO8601WeekNumber(date:Date):number { - let dayOfWeekOnFirst = (new Date(date.getFullYear(), 0, 1)).getDay(); - let firstThurs = new Date(date.getFullYear(), 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); - let thisThurs = new Date(date.getFullYear(), date.getMonth(), date.getDate() + (4 - date.getDay())); - let diff = +thisThurs - +firstThurs; - return (1 + Math.round(diff / 6.048e8)); // 6.048e8 ms per week + return className; } private static compareEventByStartOffset(eventA:IDisplayEvent, eventB:IDisplayEvent):number { @@ -816,7 +984,7 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges } select(selectedTime:Date, events:IDisplayEvent[]) { - var disabled = false; + let disabled = false; if (this.markDisabled) { disabled = this.markDisabled(selectedTime); } @@ -830,7 +998,7 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges placeEvents(orderedEvents:IDisplayEvent[]) { this.calculatePosition(orderedEvents); - WeekViewComponent.calculateWidth(orderedEvents); + WeekViewComponent.calculateWidth(orderedEvents, this.hourRange, this.hourParts); } placeAllDayEvents(orderedEvents:IDisplayEvent[]) { @@ -848,7 +1016,7 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges if (earlyEvent.endIndex <= lateEvent.startIndex) { return false; } else { - return !(earlyEvent.endIndex - lateEvent.startIndex === 1 && earlyEvent.endOffset + lateEvent.startOffset > this.hourParts); + return !(earlyEvent.endIndex - lateEvent.startIndex === 1 && earlyEvent.endOffset + lateEvent.startOffset >= this.hourParts); } } @@ -878,16 +1046,23 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges events[i].position = maxColumn++; } } + + if (this.dir === 'rtl') { + for (let i = 0; i < len; i += 1) { + events[i].position = maxColumn - 1 - events[i].position; + } + } } - private static calculateWidth(orderedEvents:IDisplayEvent[]) { - let cells = new Array(24); + private static calculateWidth(orderedEvents:IDisplayEvent[], size:number, hourParts:number) { + let totalSize = size * hourParts, + cells = new Array(totalSize); // sort by position in descending order, the right most columns should be calculated first orderedEvents.sort((eventA, eventB) => { return eventB.position - eventA.position; }); - for (let i = 0; i < 24; i += 1) { + for (let i = 0; i < totalSize; i += 1) { cells[i] = { calculated: false, events: [] @@ -896,8 +1071,8 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges let len = orderedEvents.length; for (let i = 0; i < len; i += 1) { let event = orderedEvents[i]; - let index = event.startIndex; - while (index < event.endIndex) { + let index = event.startIndex * hourParts + event.startOffset; + while (index < event.endIndex * hourParts - event.endOffset) { cells[index].events.push(event); index += 1; } @@ -911,8 +1086,8 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges event.overlapNumber = overlapNumber; let eventQueue = [event]; while ((event = eventQueue.shift())) { - let index = event.startIndex; - while (index < event.endIndex) { + let index = event.startIndex * hourParts + event.startOffset; + while (index < event.endIndex * hourParts - event.endOffset) { if (!cells[index].calculated) { cells[index].calculated = true; if (cells[index].events) { @@ -934,7 +1109,52 @@ export class WeekViewComponent implements ICalendarComponent, OnInit, OnChanges } } - eventSelected(event:IEvent) { - this.onEventSelected.emit(event); + updateCurrentView(currentViewStartDate:Date, view:IWeekView) { + let currentCalendarDate = this.calendarService.currentDate, + today = new Date(), + oneDay = 86400000, + selectedDayDifference = Math.floor((currentCalendarDate.getTime() - currentViewStartDate.getTime() - (currentCalendarDate.getTimezoneOffset() - currentViewStartDate.getTimezoneOffset()) * 60000) / oneDay), + currentDayDifference = Math.floor((today.getTime() - currentViewStartDate.getTime() - (today.getTimezoneOffset() - currentViewStartDate.getTimezoneOffset()) * 60000) / oneDay); + + for (let r = 0; r < 7; r += 1) { + view.dates[r].selected = false; + } + + if (selectedDayDifference >= 0 && selectedDayDifference < 7 && this.autoSelect) { + view.dates[selectedDayDifference].selected = true; + } + + if (currentDayDifference >= 0 && currentDayDifference < 7) { + view.dates[currentDayDifference].current = true; + } + } + + daySelected(viewDate:IWeekViewDateRow) { + let selectedDate = viewDate.date, + dates = this.views[this.currentViewIndex].dates, + currentViewStartDate = this.range.startTime, + oneDay = 86400000, + selectedDayDifference = Math.floor((selectedDate.getTime() - currentViewStartDate.getTime() - (selectedDate.getTimezoneOffset() - currentViewStartDate.getTimezoneOffset()) * 60000) / oneDay); + + this.calendarService.setCurrentDate(selectedDate); + + for (let r = 0; r < 7; r += 1) { + dates[r].selected = false; + } + + if (selectedDayDifference >= 0 && selectedDayDifference < 7) { + dates[selectedDayDifference].selected = true; + } + + let disabled = false; + if (this.markDisabled) { + disabled = this.markDisabled(selectedDate); + } + + this.onTimeSelected.emit({selectedTime: selectedDate, events: viewDate.events.map(e => e.event), disabled: disabled}); + } + + setScrollPosition(scrollPosition:number) { + this.initScrollPosition = scrollPosition; } } diff --git a/tsconfig-aot.json b/tsconfig-aot.json index e8b075f4..bc0310d3 100644 --- a/tsconfig-aot.json +++ b/tsconfig-aot.json @@ -22,9 +22,5 @@ ], "exclude": [ "node_modules" - ], - "angularCompilerOptions": { - "skipMetadataEmit" : false, - "genDir": "./aot/waste" - } + ] } diff --git a/tsconfig.json b/tsconfig.json index 135dfa3f..fa7760e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,11 @@ "outDir": "dist", "removeComments": true, "sourceMap": true, - "target": "es5" + "target": "es5", + "lib": [ + "dom", + "es2015" + ] }, "files": [ "src/calendar.ts", @@ -19,7 +23,7 @@ "src/monthview.ts", "src/weekview.ts", "src/dayview.ts", - "typings/globals/es6-shim/index.d.ts" + "src/init-position-scroll.ts" ], "exclude": [ "node_modules" diff --git a/typings.json b/typings.json index dd20a26d..4499dc24 100644 --- a/typings.json +++ b/typings.json @@ -2,4 +2,4 @@ "globalDependencies": { "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#6697d6f7dadbf5773cb40ecda35a76027e0783b2" } -} \ No newline at end of file +}