File

src/components/week/calendarWeekView.component.ts

Description

Shows all events on a given week. Example usage:

<mwl-calendar-week-view
  [viewDate]="viewDate"
  [events]="events">
</mwl-calendar-week-view>

Implements

OnChanges OnInit OnDestroy

Metadata

selector mwl-calendar-week-view
template
<div class="cal-week-view" #weekViewContainer>
  <mwl-calendar-week-view-header
    [days]="days"
    [locale]="locale"
    [customTemplate]="headerTemplate"
    (dayHeaderClicked)="dayHeaderClicked.emit($event)"
    (eventDropped)="eventTimesChanged.emit($event)">
  </mwl-calendar-week-view-header>
  <div *ngFor="let eventRow of eventRows" #eventRowContainer class="cal-events-row">
    <div
      *ngFor="let weekEvent of eventRow.row"
      #event
      class="cal-event-container"
      [class.cal-draggable]="weekEvent.event.draggable"
      [class.cal-starts-within-week]="!weekEvent.startsBeforeWeek"
      [class.cal-ends-within-week]="!weekEvent.endsAfterWeek"
      [ngClass]="weekEvent.event?.cssClass"
      [style.width]="((100 / days.length) * weekEvent.span) + '%'"
      [style.marginLeft]="((100 / days.length) * weekEvent.offset) + '%'"
      mwlResizable
      [resizeEdges]="{left: weekEvent.event?.resizable?.beforeStart, right: weekEvent.event?.resizable?.afterEnd}"
      [resizeSnapGrid]="{left: dayColumnWidth, right: dayColumnWidth}"
      [validateResize]="validateResize"
      (resizeStart)="resizeStarted(weekViewContainer, weekEvent, $event)"
      (resizing)="resizing(weekEvent, $event, dayColumnWidth)"
      (resizeEnd)="resizeEnded(weekEvent)"
      mwlDraggable
      [dragAxis]="{x: weekEvent.event.draggable && currentResizes.size === 0, y: false}"
      [dragSnapGrid]="{x: dayColumnWidth}"
      [validateDrag]="validateDrag"
      (dragStart)="dragStart(weekViewContainer, event)"
      (dragEnd)="eventDragged(weekEvent, $event.x, dayColumnWidth)">
      <mwl-calendar-week-view-event
        [weekEvent]="weekEvent"
        [tooltipPlacement]="tooltipPlacement"
        [tooltipTemplate]="tooltipTemplate"
        [tooltipAppendToBody]="tooltipAppendToBody"
        [customTemplate]="eventTemplate"
        [eventTitleTemplate]="eventTitleTemplate"
        (eventClicked)="eventClicked.emit({event: weekEvent.event})">
      </mwl-calendar-week-view-event>
    </div>
  </div>
</div>

Index

Inputs
Outputs

Inputs

events

Type: CalendarEvent[]

eventTemplate

A custom template to use for week view events

Type: TemplateRef<any>

eventTitleTemplate

A custom template to use for event titles

Type: TemplateRef<any>

excludeDays

An array of day indexes (0 = sunday, 1 = monday etc) that will be hidden on the view

Type: number[]

headerTemplate

A custom template to use to replace the header

Type: TemplateRef<any>

locale

The locale used to format dates

Type: string

precision

The precision to display events. days will round event start and end dates to the nearest day and minutes will not do this rounding

Type: "days" | "minutes"

Default value: days

refresh

An observable that when emitted on will re-render the current view

Type: Subject<any>

tooltipAppendToBody

Whether to append tooltips to the body or next to the trigger element

Type: boolean

Default value: true

tooltipPlacement

The placement of the event tooltip

Type: string

Default value: bottom

tooltipTemplate

A custom template to use for the event tooltips

Type: TemplateRef<any>

viewDate

The current view date

Type: Date

weekendDays

An array of day indexes (0 = sunday, 1 = monday etc) that indicate which days are weekends

Type: number[]

weekStartsOn

The start number of the week

Type: number

Outputs

beforeViewRender

An output that will be called before the view is rendered for the current week. If you add the cssClass property to a day in the header it will add that class to the cell element in the template

$event type: EventEmitter<literal type>
dayHeaderClicked

Called when a header week day is clicked. Adding a cssClass property on $event.day will add that class to the header element

$event type: EventEmitter<literal type>
eventClicked

Called when the event title is clicked

$event type: EventEmitter<literal type>
eventTimesChanged

Called when an event is resized or dragged and dropped

$event type: EventEmitter<CalendarEventTimesChangedEvent>
import {
  Component,
  Input,
  Output,
  EventEmitter,
  ChangeDetectorRef,
  OnChanges,
  OnInit,
  OnDestroy,
  LOCALE_ID,
  Inject,
  TemplateRef
} from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import {
  WeekDay,
  CalendarEvent,
  WeekViewEvent,
  WeekViewEventRow
} from 'calendar-utils';
import { ResizeEvent } from 'angular-resizable-element';
import addDays from 'date-fns/add_days';
import { CalendarDragHelper } from '../../providers/calendarDragHelper.provider';
import { CalendarResizeHelper } from '../../providers/calendarResizeHelper.provider';
import { CalendarEventTimesChangedEvent } from '../../interfaces/calendarEventTimesChangedEvent.interface';
import { CalendarUtils } from '../../providers/calendarUtils.provider';

export interface WeekViewEventResize {
  originalOffset: number;
  originalSpan: number;
  edge: string;
}

/**
 * Shows all events on a given week. Example usage:
 *
 * ```typescript
 * <mwl-calendar-week-view
 *  [viewDate]="viewDate"
 *  [events]="events">
 * </mwl-calendar-week-view>
 * ```
 */
@Component({
  selector: 'mwl-calendar-week-view',
  template: `
    <div class="cal-week-view" #weekViewContainer>
      <mwl-calendar-week-view-header
        [days]="days"
        [locale]="locale"
        [customTemplate]="headerTemplate"
        (dayHeaderClicked)="dayHeaderClicked.emit($event)"
        (eventDropped)="eventTimesChanged.emit($event)">
      </mwl-calendar-week-view-header>
      <div *ngFor="let eventRow of eventRows" #eventRowContainer class="cal-events-row">
        <div
          *ngFor="let weekEvent of eventRow.row"
          #event
          class="cal-event-container"
          [class.cal-draggable]="weekEvent.event.draggable"
          [class.cal-starts-within-week]="!weekEvent.startsBeforeWeek"
          [class.cal-ends-within-week]="!weekEvent.endsAfterWeek"
          [ngClass]="weekEvent.event?.cssClass"
          [style.width]="((100 / days.length) * weekEvent.span) + '%'"
          [style.marginLeft]="((100 / days.length) * weekEvent.offset) + '%'"
          mwlResizable
          [resizeEdges]="{left: weekEvent.event?.resizable?.beforeStart, right: weekEvent.event?.resizable?.afterEnd}"
          [resizeSnapGrid]="{left: dayColumnWidth, right: dayColumnWidth}"
          [validateResize]="validateResize"
          (resizeStart)="resizeStarted(weekViewContainer, weekEvent, $event)"
          (resizing)="resizing(weekEvent, $event, dayColumnWidth)"
          (resizeEnd)="resizeEnded(weekEvent)"
          mwlDraggable
          [dragAxis]="{x: weekEvent.event.draggable && currentResizes.size === 0, y: false}"
          [dragSnapGrid]="{x: dayColumnWidth}"
          [validateDrag]="validateDrag"
          (dragStart)="dragStart(weekViewContainer, event)"
          (dragEnd)="eventDragged(weekEvent, $event.x, dayColumnWidth)">
          <mwl-calendar-week-view-event
            [weekEvent]="weekEvent"
            [tooltipPlacement]="tooltipPlacement"
            [tooltipTemplate]="tooltipTemplate"
            [tooltipAppendToBody]="tooltipAppendToBody"
            [customTemplate]="eventTemplate"
            [eventTitleTemplate]="eventTitleTemplate"
            (eventClicked)="eventClicked.emit({event: weekEvent.event})">
          </mwl-calendar-week-view-event>
        </div>
      </div>
    </div>
  `
})
export class CalendarWeekViewComponent implements OnChanges, OnInit, OnDestroy {
  /**
   * The current view date
   */
  @Input() viewDate: Date;

  /**
   * An array of events to display on view
   * The schema is available here: https://github.com/mattlewis92/calendar-utils/blob/c51689985f59a271940e30bc4e2c4e1fee3fcb5c/src/calendarUtils.ts#L49-L63
   */
  @Input() events: CalendarEvent[] = [];

  /**
   * An array of day indexes (0 = sunday, 1 = monday etc) that will be hidden on the view
   */
  @Input() excludeDays: number[] = [];

  /**
   * An observable that when emitted on will re-render the current view
   */
  @Input() refresh: Subject<any>;

  /**
   * The locale used to format dates
   */
  @Input() locale: string;

  /**
   * The placement of the event tooltip
   */
  @Input() tooltipPlacement: string = 'bottom';

  /**
   * A custom template to use for the event tooltips
   */
  @Input() tooltipTemplate: TemplateRef<any>;

  /**
   * Whether to append tooltips to the body or next to the trigger element
   */
  @Input() tooltipAppendToBody: boolean = true;

  /**
   * The start number of the week
   */
  @Input() weekStartsOn: number;

  /**
   * A custom template to use to replace the header
   */
  @Input() headerTemplate: TemplateRef<any>;

  /**
   * A custom template to use for week view events
   */
  @Input() eventTemplate: TemplateRef<any>;

  /**
   * A custom template to use for event titles
   */
  @Input() eventTitleTemplate: TemplateRef<any>;

  /**
   * The precision to display events.
   * `days` will round event start and end dates to the nearest day and `minutes` will not do this rounding
   */
  @Input() precision: 'days' | 'minutes' = 'days';

  /**
   * An array of day indexes (0 = sunday, 1 = monday etc) that indicate which days are weekends
   */
  @Input() weekendDays: number[];

  /**
   * Called when a header week day is clicked. Adding a `cssClass` property on `$event.day` will add that class to the header element
   */
  @Output()
  dayHeaderClicked: EventEmitter<{ day: WeekDay }> = new EventEmitter<{
    day: WeekDay;
  }>();

  /**
   * Called when the event title is clicked
   */
  @Output()
  eventClicked: EventEmitter<{ event: CalendarEvent }> = new EventEmitter<{
    event: CalendarEvent;
  }>();

  /**
   * Called when an event is resized or dragged and dropped
   */
  @Output()
  eventTimesChanged: EventEmitter<
    CalendarEventTimesChangedEvent
  > = new EventEmitter<CalendarEventTimesChangedEvent>();

  /**
   * An output that will be called before the view is rendered for the current week.
   * If you add the `cssClass` property to a day in the header it will add that class to the cell element in the template
   */
  @Output()
  beforeViewRender: EventEmitter<{ header: WeekDay[] }> = new EventEmitter();

  /**
   * @hidden
   */
  days: WeekDay[];

  /**
   * @hidden
   */
  eventRows: WeekViewEventRow[] = [];

  /**
   * @hidden
   */
  refreshSubscription: Subscription;

  /**
   * @hidden
   */
  currentResizes: Map<WeekViewEvent, WeekViewEventResize> = new Map();

  /**
   * @hidden
   */
  validateDrag: (args: any) => boolean;

  /**
   * @hidden
   */
  validateResize: (args: any) => boolean;

  /**
   * @hidden
   */
  dayColumnWidth: number;

  /**
   * @hidden
   */
  constructor(
    private cdr: ChangeDetectorRef,
    private utils: CalendarUtils,
    @Inject(LOCALE_ID) locale: string
  ) {
    this.locale = locale;
  }

  /**
   * @hidden
   */
  ngOnInit(): void {
    if (this.refresh) {
      this.refreshSubscription = this.refresh.subscribe(() => {
        this.refreshAll();
        this.cdr.markForCheck();
      });
    }
  }

  /**
   * @hidden
   */
  ngOnChanges(changes: any): void {
    if (changes.viewDate || changes.excludeDays || changes.weekendDays) {
      this.refreshHeader();
    }

    if (changes.events || changes.viewDate || changes.excludeDays) {
      this.refreshBody();
    }
  }

  /**
   * @hidden
   */
  ngOnDestroy(): void {
    if (this.refreshSubscription) {
      this.refreshSubscription.unsubscribe();
    }
  }

  /**
   * @hidden
   */
  resizeStarted(
    weekViewContainer: HTMLElement,
    weekEvent: WeekViewEvent,
    resizeEvent: ResizeEvent
  ): void {
    this.currentResizes.set(weekEvent, {
      originalOffset: weekEvent.offset,
      originalSpan: weekEvent.span,
      edge: typeof resizeEvent.edges.left !== 'undefined' ? 'left' : 'right'
    });
    this.dayColumnWidth = this.getDayColumnWidth(weekViewContainer);
    const resizeHelper: CalendarResizeHelper = new CalendarResizeHelper(
      weekViewContainer,
      this.dayColumnWidth
    );
    this.validateResize = ({ rectangle }) =>
      resizeHelper.validateResize({ rectangle });
    this.cdr.markForCheck();
  }

  /**
   * @hidden
   */
  resizing(
    weekEvent: WeekViewEvent,
    resizeEvent: ResizeEvent,
    dayWidth: number
  ): void {
    const currentResize: WeekViewEventResize = this.currentResizes.get(
      weekEvent
    );

    if (resizeEvent.edges.left) {
      const diff: number = Math.round(+resizeEvent.edges.left / dayWidth);
      weekEvent.offset = currentResize.originalOffset + diff;
      weekEvent.span = currentResize.originalSpan - diff;
    } else if (resizeEvent.edges.right) {
      const diff: number = Math.round(+resizeEvent.edges.right / dayWidth);
      weekEvent.span = currentResize.originalSpan + diff;
    }
  }

  /**
   * @hidden
   */
  resizeEnded(weekEvent: WeekViewEvent): void {
    const currentResize: WeekViewEventResize = this.currentResizes.get(
      weekEvent
    );

    let daysDiff: number;
    if (currentResize.edge === 'left') {
      daysDiff = weekEvent.offset - currentResize.originalOffset;
    } else {
      daysDiff = weekEvent.span - currentResize.originalSpan;
    }

    weekEvent.offset = currentResize.originalOffset;
    weekEvent.span = currentResize.originalSpan;

    let newStart: Date = weekEvent.event.start;
    let newEnd: Date = weekEvent.event.end;
    if (currentResize.edge === 'left') {
      newStart = addDays(newStart, daysDiff);
    } else if (newEnd) {
      newEnd = addDays(newEnd, daysDiff);
    }

    this.eventTimesChanged.emit({ newStart, newEnd, event: weekEvent.event });
    this.currentResizes.delete(weekEvent);
  }

  /**
   * @hidden
   */
  eventDragged(
    weekEvent: WeekViewEvent,
    draggedByPx: number,
    dayWidth: number
  ): void {
    const daysDragged: number = draggedByPx / dayWidth;
    const newStart: Date = addDays(weekEvent.event.start, daysDragged);
    let newEnd: Date;
    if (weekEvent.event.end) {
      newEnd = addDays(weekEvent.event.end, daysDragged);
    }

    this.eventTimesChanged.emit({ newStart, newEnd, event: weekEvent.event });
  }

  /**
   * @hidden
   */
  getDayColumnWidth(eventRowContainer: HTMLElement): number {
    return Math.floor(eventRowContainer.offsetWidth / this.days.length);
  }

  /**
   * @hidden
   */
  dragStart(weekViewContainer: HTMLElement, event: HTMLElement): void {
    this.dayColumnWidth = this.getDayColumnWidth(weekViewContainer);
    const dragHelper: CalendarDragHelper = new CalendarDragHelper(
      weekViewContainer,
      event
    );
    this.validateDrag = ({ x, y }) =>
      this.currentResizes.size === 0 && dragHelper.validateDrag({ x, y });
    this.cdr.markForCheck();
  }

  private refreshHeader(): void {
    this.days = this.utils.getWeekViewHeader({
      viewDate: this.viewDate,
      weekStartsOn: this.weekStartsOn,
      excluded: this.excludeDays,
      weekendDays: this.weekendDays
    });
    this.beforeViewRender.emit({
      header: this.days
    });
  }

  private refreshBody(): void {
    this.eventRows = this.utils.getWeekView({
      events: this.events,
      viewDate: this.viewDate,
      weekStartsOn: this.weekStartsOn,
      excluded: this.excludeDays,
      precision: this.precision,
      absolutePositionedEvents: true
    });
  }

  private refreshAll(): void {
    this.refreshHeader();
    this.refreshBody();
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""