Resource Hover Tooltip

Displaying information in a tooltip can come in handy when space is tight but information matters. When hovering over a resource cell, the onResourceHoverIn and onResourceHoverOut lifecycle events are triggered, which can be used to show/hide rich tooltips. Information and actions shown here won't clutter the main view, keep the timeline clean while still giving quick access to content that helps.

Do you want to learn more about customizing the resource header?  Take a look at this example →
Get started with this example

Demo

Loading the live demo

Preview in full screen

Hover over a resource row in the Timeline to open a tooltip popup with scheduled hours — keeps the timeline view clean.

Code

import { CommonModule } from '@angular/common';
import { Component, ViewChild } from '@angular/core';
import {
  formatDate,
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscModule,
  MbscPopup,
  MbscPopupPositionEvent,
  MbscResource,
  MbscResourceHoverEvent,
  Notifications,
  setOptions ,
} from '@mobiscroll/angular';

setOptions({
  locale:  ,
  theme: ' ',
  themeVariant: ' '
});

interface MyResource extends MbscResource {
  profession: string;
  avatar: string;
  cost: string;
}

@Component({
  selector: 'app-root', 
  templateUrl: './app.component.html',
  standalone: true,
  imports: [CommonModule, MbscModule],
})
export class AppComponent {
  constructor(private notify: Notifications) {}

  @ViewChild('popup', { static: false })
  popup!: MbscPopup;

  myResources: MyResource[] = [
    {
      id: 'res1',
      name: 'Adam Miller',
      color: '#F39C12',
      profession: 'Mason',
      avatar: 'https://img.mobiscroll.com/demos/m1.png',
      cost: '15',
    },
    {
      id: 'res2',
      name: 'Emily Carter',
      color: '#76e083',
      profession: 'Electrician',
      avatar: 'https://img.mobiscroll.com/demos/f1.png',
      cost: '20',
    },
    {
      id: 'res3',
      name: 'James Brown',
      color: '#b13f49',
      profession: 'Carpenter',
      avatar: 'https://img.mobiscroll.com/demos/m2.png',
      cost: '18',
    },
    {
      id: 'res4',
      name: 'Daniel Wilson',
      color: '#e25dd2',
      profession: 'Welder',
      avatar: 'https://img.mobiscroll.com/demos/m3.png',
      cost: '22',
    },
    {
      id: 'res5',
      name: 'Benjamin Harris',
      color: '#7056ff',
      profession: 'Plumber',
      avatar: 'https://img.mobiscroll.com/demos/m4.png',
      cost: '20',
    },
    {
      id: 'res6',
      name: 'Olivia Anderson',
      color: '#56aaff',
      profession: 'Concrete Finisher',
      avatar: 'https://img.mobiscroll.com/demos/f2.png',
      cost: '15',
    },
    {
      id: 'res7',
      name: 'Emma Thompson',
      color: '#84852f',
      profession: 'Steelworker',
      avatar: 'https://img.mobiscroll.com/demos/f3.png',
      cost: '18',
    },
    {
      id: 'res8',
      name: 'Natalie Roberts',
      color: '#ff6e56',
      profession: 'Painter',
      avatar: 'https://img.mobiscroll.com/demos/f4.png',
      cost: '25',
    },
  ];

  myEvents: MbscCalendarEvent[] = [
    {
      start: '2026-10-24T12:00',
      end: '2026-10-24T15:00',
      title: 'Repoint Brick Facade',
      resource: 'res1',
    },
    {
      start: '2026-10-24T09:00',
      end: '2026-10-24T12:00',
      title: 'Install Custom Wood Trim',
      resource: 'res3',
    },
    {
      start: '2026-10-24T14:00',
      end: '2026-10-24T18:00',
      title: 'Repair Steel Stair Treads',
      resource: 'res4',
    },
    {
      start: '2026-10-24T10:00',
      end: '2026-10-24T13:00',
      title: 'Pour and Finish Driveway Slab',
      resource: 'res6',
    },
    {
      start: '2026-10-24T11:00',
      end: '2026-10-24T16:00',
      title: 'Paint Interior Drywall',
      resource: 'res8',
    },
    {
      start: '2026-10-25T08:00',
      end: '2026-10-25T11:00',
      title: 'Block Wall Construction',
      resource: 'res1',
    },
    {
      start: '2026-10-25T14:00',
      end: '2026-10-25T16:00',
      title: 'Task 2',
      resource: 'Paver Installation',
    },
    {
      start: '2026-10-25T12:00',
      end: '2026-10-25T17:00',
      title: 'Install ceiling fan',
      resource: 'res2',
    },
    {
      start: '2026-10-25T10:00',
      end: '2026-10-25T14:00',
      title: 'Roof Beam Replacement',
      resource: 'res3',
    },
    {
      start: '2026-10-25T07:00',
      end: '2026-10-25T12:00',
      title: 'Custom Metalworks Creation',
      resource: 'res4',
    },
    {
      start: '2026-10-25T14:00',
      end: '2026-10-25T17:00',
      title: 'Pipe Welding',
      resource: 'res4',
    },
    {
      start: '2026-10-10T08:00',
      end: '2026-10-11T20:00',
      title: 'Leak Detection & Repair',
      resource: 'res5',
    },
    {
      start: '2026-10-25T13:00',
      end: '2026-10-25T17:00',
      title: 'Faucet & Sink Fitting',
      resource: 'res5',
    },
    {
      start: '2026-10-25T18:00',
      end: '2026-10-25T20:00',
      title: 'Drainage System Setup',
      resource: 'res5',
    },
    {
      start: '2026-10-25T09:00',
      end: '2026-10-25T13:00',
      title: 'Surface Polishing',
      resource: 'res6',
    },
    {
      start: '2026-10-25T08:00',
      end: '2026-10-25T10:00',
      title: 'Structural Steel Inspections',
      resource: 'res7',
    },
    {
      start: '2026-10-25T13:00',
      end: '2026-10-25T16:00',
      title: 'Metal Structure Assembly',
      resource: 'res7',
    },
    {
      start: '2026-10-25T17:00',
      end: '2026-10-25T19:00',
      title: 'Heavy Steel Beam Placement',
      resource: 'res7',
    },
    {
      start: '2026-10-25T09:00',
      end: '2026-10-25T12:00',
      title: 'Exterior House Painting',
      resource: 'res8',
    },
    {
      start: '2026-10-25T15:00',
      end: '2026-10-25T18:00',
      title: 'Deck Staining & Sealing',
      resource: 'res8',
    },
    {
      start: '2026-10-26T12:00',
      end: '2026-10-26T15:00',
      title: 'Troubleshoot Faulty Breaker',
      resource: 'res2',
    },
    {
      start: '2026-10-26T10:00',
      end: '2026-10-26T13:00',
      title: 'Frame Interior Partitions',
      resource: 'res3',
    },
    {
      start: '2026-10-26T16:00',
      end: '2026-10-26T20:00',
      title: 'Weld Structural Beam Connections',
      resource: 'res4',
    },
    {
      start: '2026-10-26T12:00',
      end: '2026-10-26T16:00',
      title: 'Apply Smooth Trowel Finish to Basement Floor',
      resource: 'res6',
    },
  ];

  resourceAvatar = '';
  resourceName: string | undefined = '';
  resourceCost = '';
  resourceDate = '';
  resourceTotal = '';

  currentResource: MbscResource | undefined;
  hoveredResourceElm: HTMLElement | null = null;
  mySelectedDate: Date = new Date();

  openTimer?: ReturnType<typeof setTimeout>;
  closeTimer?: ReturnType<typeof setTimeout>;

  myView: MbscEventcalendarView = {
    timeline: {
      type: 'day',
      startTime: '07:00',
      endTime: '22:00',
    },
  };

  getTotalHoursForResource(resourceId: string | number): number {
    return this.myEvents
      .filter((e: MbscCalendarEvent) => e.resource === resourceId)
      .reduce((sum: number, e: MbscCalendarEvent) => {
        const start: Date = new Date(e.start as Date);
        const end: Date = new Date(e.end as Date);
        const hours = (end.getTime() - start.getTime()) / (1000 * 60 * 60);
        return sum + hours;
      }, 0);
  }

  openTooltip(resource: MbscResource, target: HTMLElement) {
    clearTimeout(this.closeTimer);

    this.openTimer = setTimeout(() => {
      const totalHours = this.getTotalHoursForResource(resource.id);

      this.currentResource = resource;
      this.hoveredResourceElm = target;

      this.resourceAvatar = resource['avatar'];
      this.resourceName = resource.name;
      this.resourceCost = '$' + resource['cost'];
      this.resourceDate = formatDate('D DDD MMM YYYY', this.mySelectedDate);
      this.resourceTotal = totalHours + 'h, $' + totalHours * resource['cost'];
      target.classList.add('mds-resource-info-hover');

      this.popup.position();
      this.popup.open();

      this.openTimer = undefined;
    }, 100);
  }

  closeTooltip() {
    clearTimeout(this.openTimer);
    clearTimeout(this.closeTimer);

    this.closeTimer = setTimeout(() => {
      this.popup.close();
      this.closeTimer = undefined;
    }, 200);
  }

  handlePopupPosition(args: MbscPopupPositionEvent): boolean {
    const popupElm: HTMLElement | null = args.target.querySelector('.mbsc-popup')!;
    const rect: DOMRect = this.hoveredResourceElm!.getBoundingClientRect();

    popupElm.style.top = rect.top - 10 + 'px';

    if (args.inst!.s.rtl) {
      popupElm.style.right = window.innerWidth - rect.left + 10 + 'px';
    } else {
      popupElm.style.left = rect.right + 10 + 'px';
    }

    return false; // Prevent default positioning
  }

  handleResourceHoverIn(args: MbscResourceHoverEvent): void {
    this.openTooltip(args.resource, args.target);
  }

  handleResourceHoverOut(args: MbscResourceHoverEvent): void {
    args.target.classList.remove('mds-resource-info-hover');
    this.closeTooltip();
  }

  handlePopupMouseEnter(): void {
    clearTimeout(this.closeTimer);
  }

  handlePopupMouseLeave(): void {
    this.closeTooltip();
  }

  handlePay(): void {
    this.popup.close();
    this.notify.toast({
      message: this.currentResource!.name + ' paid',
    });
  }
}

Duplicate Session

You have signed in on a different machine. To keep everything in sync, you have been signed out of this session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.

Duplicate Session

We noticed you are logged in on a different machine and have closed your old session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.