Features

  • The Angular Scheduler from DayPilot Pro for JavaScript supports several ways of showing event details on hover
  • The built-in bubble tooltip can show static HTML, dynamic HTML built on the client side, dynamic HTML built on the server side or a dynamic Angular component
  • The tutorial ncludes a trial version of DayPilot Pro for JavaScript (see License below)

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript. Buy a license.

Introduction

For introduction to using the Angular Scheduler component please see the basic tutorial:

Bubble Tooltip for Scheduler Events

angular-scheduler-static-event-tooltip.png

The Angular Scheduler component uses a built-in DayPilot.Bubble class to display a custom tooltip for Scheduler events.

The easiest way to display a static event tooltip is to add bubbleHtml property to the event data object:

events: any[] = [
  {
    id: '1',
    resource: 'R2',
    start: '2018-06-08',
    end: '2018-06-12',
    text: 'Scheduler Event 1',
    bubbleHtml: "<div style='font-weight:bold'>Event Details</div><div>Scheduler Event 1</div>"
  }
  // ...
];

Loading Tooltip HTML from the Server

Instead of the static tooltip HTML, you can use onLoad event handler of the Bubble object to define custom HTML on the fly.

Synchronous Event Handler

angular-scheduler-dynamic-event-tooltip-synchronous.png

You can return the HTML synchronously by setting args.html property. You can use this approach if the event object has all the data you need to build the HTML.

config: any = {
  // ...
  bubble: new DayPilot.Bubble({
    onLoad: args => {
      let event = args.source;
      args.html = "<div style='font-weight:bold'>Event Details</div><div>" + event.text() + "</div><div>Starting on " + event.start().toString("MMMM d, yyyy") + "</div>";
    }
  })
};

Asynchronous Event Handler

angular-scheduler-dynamic-event-tooltip-asynchronous.png

If you need to load additional data from the server you can activate asynchronous loading. The Scheduler will wait with displaying the tooltip until you call args.loaded() method.

You can use the asynchronous call to load more data from the server using an AJAX call.

config: any = {
  // ...
  bubble: new DayPilot.Bubble({
    onLoad: args => {
      args.async = true;
      
      let event = args.source;
      this.http.get("/getEventDetails/" + event.id()).subscribe(result => {
        args.html = result;  
        args.loaded();
      });
    }
  })
};

Dynamic Angular Component 

angular-scheduler-dynamic-event-tooltip-component.png

Instead of supplying the tooltip HTML you can also insert an Angular component into the bubble tooltip. You need to use DayPilot.Bubble.onDomAdd event handler to supply a custom DOM element that will be added to the bubble body.

First, you need to create the component dynamically:

const factory = this.resolver.resolveComponentFactory(TooltipComponent);
const component: ComponentRef<TooltipComponent> = factory.create(this.injector);

Then set the instance properties. We want to pass the Scheduler reference (DayPilot.Scheduler object) and the event reference (DayPilot.Event object) to the TooltipComponent:

component.instance.scheduler = this.scheduler.control;
component.instance.event = e;
component.changeDetectorRef.detectChanges();

It's necessary to add the dynamically-created component to the Angular component tree:

this.app.attachView(component.hostView);

The steps above are used in createTooltipComponent() helper method. It creates the component, initializes the data and returns the ComponentRef class:

createTooltipComponent(e: any): ComponentRef<TooltipComponent> {
  const factory = this.resolver.resolveComponentFactory(TooltipComponent);
  const component: ComponentRef<TooltipComponent> = factory.create(this.injector);

  component.instance.scheduler = this.scheduler.control;
  component.instance.event = e;
  component.changeDetectorRef.detectChanges();

  this.app.attachView(component.hostView);

  return component;
}

Use onDomAdd event handler of the DayPilot.Bubble class to add the TooltipComponent DOM element to the tooltip using args.element property. This element will be used for the bubble content instead of the default HTML:

bubble: new DayPilot.Bubble({
  onDomAdd: args => {
    const component = this.createTooltipComponent(args.source);
    args.element = component.location.nativeElement;
  },
  // ...
})

When the bubble tooltip is destroyed (and removed from the DOM) it calls onDomRemove event handler. It's a good place for the cleanup code.

bubble: new DayPilot.Bubble({
  onDomAdd: args => {
    console.log("Creating TooltipComponent");
    const component = this.createTooltipComponent(args.source);
    args.element = component.location.nativeElement;
    args.component = component;
  },
  onDomRemove: args => {
    console.log("Destroying TooltipComponent");
    this.app.detachView(args.component.hostView);
    args.component.destroy();
  }
})

Full Source Code

scheduler.component.ts

import {
  Component,
  ViewChild,
  AfterViewInit,
  ComponentRef,
  ApplicationRef,
  Injector,
  ComponentFactoryResolver
} from '@angular/core';
import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular';
import {DataService} from './data.service';
import {TooltipComponent} from "./tooltip.component"; {}

@Component({
  selector: 'scheduler-component',
  template: `<daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,
  styles: [``]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild('scheduler')
  scheduler: DayPilotSchedulerComponent;

  events: any[] = [];

  config: any = {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    treeEnabled: true,
    eventHeight: 30,
    eventEndSpec: "Date",
    onTimeRangeSelected: function (args) {
      var dp = this;
      DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
        dp.clearSelection();
        if (!modal.result) { return; }
        dp.events.add(new DayPilot.Event({
          start: args.start,
          end: args.end,
          id: DayPilot.guid(),
          resource: args.resource,
          text: modal.result
        }));
      });
    },
    bubble: new DayPilot.Bubble({
      onDomAdd: args => {
        console.log("Creating TooltipComponent");
        const component = this.createTooltipComponent(args.source);
        args.element = component.location.nativeElement;
        args.component = component;
      },
      onDomRemove: args => {
        console.log("Destroying TooltipComponent");
        this.app.detachView(args.component.hostView);
        args.component.destroy();
      }
    })
  };

  constructor(private ds: DataService,
              private resolver: ComponentFactoryResolver,
              private injector: Injector,
              private app: ApplicationRef) {
  }

  ngAfterViewInit(): void {
    this.ds.getResources().subscribe(result => this.config.resources = result);

    const from = this.scheduler.control.visibleStart();
    const to = this.scheduler.control.visibleEnd();
    this.ds.getEvents(from, to).subscribe(result => {
      this.events = result;
    });
  }

  createTooltipComponent(e: any): ComponentRef<TooltipComponent> {
    const factory = this.resolver.resolveComponentFactory(TooltipComponent);
    const component: ComponentRef<TooltipComponent> = factory.create(this.injector);

    component.instance.scheduler = this.scheduler.control;
    component.instance.event = e;
    component.changeDetectorRef.detectChanges();

    this.app.attachView(component.hostView);

    return component;
  }

}

tooltip.component.ts

import {Component} from '@angular/core';
import {DayPilot} from "daypilot-pro-angular";

@Component({
  selector: 'tooltip-component',
  template: `
    <div class="main">
      <div class="title">{{start}} - {{end}}</div>
      <div class="text">{{text}}</div>
      <div><a href="#" (click)="clickDelete($event)">Delete</a></div>
    </div>
  `,
  styles: [`
    .main {
      width: 200px;
      padding: 5px;
    }
    .title {
      font-weight: bold;
    }
    .text {
      margin: 10px 0px;
    }
    a {
      color: #000;
    }
  `]
})
export class TooltipComponent  {

  public event: DayPilot.Event;
  public scheduler: DayPilot.Scheduler;

  clickDelete(e) {
    e.preventDefault();
    this.scheduler.events.remove(this.event);
  }

  get text() {
    return this.event.text();
  }

  get start() {
    return this.event.start().toString("MMMM d, yyyy");
  }
  get end() {
    return this.event.end().toString("MMMM d, yyyy");
  }
}

data.service.ts

import {Injectable} from '@angular/core';
import {DayPilot} from 'daypilot-pro-angular';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';

@Injectable()
export class DataService {

  resources: any[] = [
    { name: 'Group A', id: 'GA', expanded: true, children: [
      { name: 'Resource 1', id: 'R1' },
      { name: 'Resource 2', id: 'R2' }
    ]},
    { name: 'Group B', id: 'GB', expanded: true, children: [
      { name: 'Resource 3', id: 'R3', unavailable: true},
      { name: 'Resource 4', id: 'R4'}
    ]}
  ];

  events: any[] = [
    {
      id: '1',
      resource: 'R2',
      start: '2018-06-08',
      end: '2018-06-12',
      text: 'Scheduler Event 1',
      barColor: '#e69138',
      barBackcolor: '#fff'
    },
    {
      id: '2',
      resource: 'R3',
      start: '2018-06-02',
      end: '2018-06-05',
      text: 'Scheduler Event 2',
      barColor: '#6aa84f',
      barBackcolor: '#fff'
    },
    {
      id: '3',
      resource: 'R3',
      start: '2018-06-08',
      end: '2018-06-09',
      text: 'Scheduler Event 3',
      barColor: '#3c78d8',
      barBackcolor: '#fff'
    }
  ];

  constructor(private http: HttpClient) {
  }

  getEvents(from: DayPilot.Date, to: DayPilot.Date): Observable<any[]> {

    // simulating an HTTP request
    return new Observable(observer => {
      setTimeout(() => {
        observer.next(this.events);
      }, 200);
    });

    // return this.http.get("/api/events?from=" + from.toString() + "&to=" + to.toString());
  }

  getResources(): Observable<any[]> {

    // simulating an HTTP request
    return new Observable(observer => {
      setTimeout(() => {
        observer.next(this.resources);
      }, 200);
    });

    // return this.http.get("/api/resources");
  }

}