Overview

The JavaScript Scheduler component displays events in a grid with resources on the vertical axis and timeline on the horizontal axis.

To connect related events, it is possible to use event links - lines with arrows that connect the events and visualize relationships such as:

  • Predecessor and Successor Events: Highlighting events that must occur before or after others.

  • Conditional Dependencies: Indicating events that are conditional upon the completion of another event.

  • Parallel Events: Displaying events that occur simultaneously but may have interdependencies.

  • Resource Sharing: Showing events that share the same resources, such as personnel or equipment.

  • Milestone Connections: Linking events to milestones or deadlines to ensure alignment with project goals.

  • Task Subdivisions: Connecting primary tasks to their sub-tasks or component activities.

  • Workflow Steps: Illustrating the sequence of steps in a workflow or process.

  • Impact Analysis: Showing events that impact or are impacted by other events, useful in risk management.

This tutorial shows how to modify the event appearance, depending on whether it is linked to other events.

Dependencies:

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.

How to Detect if an Event Has Links Attached

In onBeforeEventRender (the event customization callback) you can use the links.findAllByFromTo() method to find all links pointing from or to this event.

1. Detect if there is a link pointing to the left side of an event:

const scheduler = new DayPilot.Scheduler("dp", {
  // ...
  onBeforeEventRender: args => {
    const linksLeft = scheduler.links.findAllByFromTo(null, args.data.id).filter(link => link.data.type === "FinishToStart" || !link.data.type);

    const left = linksLeft.length > 0;

    if (left) {
      args.data.cssClass = "link-left";
    }
  }
});

This code finds all links where the to property the link has a value equal to the ID of this event. Only links of FinishToStart type are included (links with no explicit type are included as well, as FinishToStart is the default type).

If at least one such link is found, the event will be marked with link-left CSS class.

2. Detect if there is an outgoing link :

const scheduler = new DayPilot.Scheduler("dp", {
  // ...
  onBeforeEventRender: args => {
    const linksRight = scheduler.links.findAllByFromTo(args.data.id, null).filter(link => link.data.type === "FinishToStart" || !link.data.type);

    const right = linksRight.length > 0;

    if (right) {
      args.data.cssClass = "link-right";
    }

  }
});

This code uses the same logic. This time it finds links where the from value is equal to the event id.

How to Style the Linked Events

First, we will modify the default theme and set a green background with white text for events. We will also remove the default border style.

In order to override the built-in style, we will make the selector more specific by prepending body.

body .scheduler_default_event_inner {
  background: #4CAF50;
  color: #ffffff;
  border: none;
  padding-left: 6px;
}

Events with a link starting at their right side (link-right) will use the following CSS that shapes it like an arrowhead:

JavaScript Scheduler - Events with a link starting at their right side

CSS

.link-right .scheduler_default_event_inner {
  right: 20px !important;
}
.link-right:after {
  content: "";
  position: absolute;
  right: 0px;
  bottom: 0;
  width: 0;
  height: 0;
  border-left: 20px solid #4CAF50;
  border-top: 20px solid transparent;
  border-bottom: 20px solid transparent;
}

Events with a link pointing to their left side are marked with the link-left CSS class:

We will add custom CSS to shape the left side as an inverted arrowhead to visually indicate that this is a continuation of the linked event.

JavaScript Scheduler - Events with a link pointing to their left side

CSS

.link-left .scheduler_default_event_inner {
  left: 20px !important;
}
.link-left:before {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0;
  height: 0;
  border-left: 20px solid white;
  border-top: 20px solid #4CAF50;
  border-bottom: 20px solid #4CAF50;
}

Full Source Code

Here is the full source code of the JavaScript Scheduler component configured to use custom appearance for linked events, including the CSS styles:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>JavaScript Scheduler: Styling Linked Events</title>

  <style>
    body .scheduler_default_event_inner {
      background: #4CAF50;
      color: #ffffff;
      border: none;
      padding-left: 6px;
    }

    .link-left .scheduler_default_event_inner {
      left: 20px !important;
    }
    .link-left:before {
      content: "";
      position: absolute;
      left: 0;
      bottom: 0;
      width: 0;
      height: 0;
      border-left: 20px solid white;
      border-top: 20px solid #4CAF50;
      border-bottom: 20px solid #4CAF50;
    }

    .link-right .scheduler_default_event_inner {
      right: 20px !important;
    }
    .link-right:after {
      content: "";
      position: absolute;
      right: 0px;
      bottom: 0;
      width: 0;
      height: 0;
      border-left: 20px solid #4CAF50;
      border-top: 20px solid transparent;
      border-bottom: 20px solid transparent;
    }
  </style>

    <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/77491/javascript-scheduler-styling-linked-events'>JavaScript Scheduler: Styling Linked Events</a></h1>
  <div><a href="https://javascript.daypilot.org/">DayPilot for JavaScript</a> - HTML5 Calendar/Scheduling Components for JavaScript/Angular/React/Vue</div>
</div>

<div class="main">
  <div id="dp"></div>
  <div class="generated">Generated using <a href="https://builder.daypilot.org/">DayPilot UI Builder</a>.</div>
</div>

<script>
  const scheduler = new DayPilot.Scheduler("dp", {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: 365,
    startDate: "2024-01-01",
    timeRangeSelectedHandling: "Enabled",
    eventHeight: 40,
    durationBarVisible: false,
    onTimeRangeSelected: async (args) => {
      const dp = args.control;
      const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
      dp.clearSelection();
      if (modal.canceled) { return; }
      dp.events.add({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: modal.result
      });
    },

    treeEnabled: true,
    onBeforeEventRender: args => {
      const linksLeft = scheduler.links.findAllByFromTo(null, args.data.id).filter(link => link.data.type === "FinishToStart" || !link.data.type);
      const linksRight = scheduler.links.findAllByFromTo(args.data.id, null).filter(link => link.data.type === "FinishToStart" || !link.data.type);

      const left = linksLeft.length > 0;
      const right = linksRight.length > 0;

      if (left) {
        args.data.cssClass = "link-left";
      }
      if (right) {
        args.data.cssClass = "link-right";
      }

    }
  });
  scheduler.init();
  scheduler.scrollTo("2024-09-01");

  const app = {
    loadData: function() {
      const resources = [
        {name: "Resource 1", id: "R1"},
        {name: "Resource 2", id: "R2"},
        {name: "Resource 3", id: "R3"},
        {name: "Resource 4", id: "R4"},
        {name: "Resource 5", id: "R5"},
        {name: "Resource 6", id: "R6"},
        {name: "Resource 7", id: "R7"},
        {name: "Resource 8", id: "R8"},
        {name: "Resource 9", id: "R9"},
      ];

      const events = [
        {start: "2024-09-02T00:00:00", end: "2024-09-05T00:00:00", id: 1, resource: "R2", text: "Event 1"},
        {start: "2024-09-05T00:00:00", end: "2024-09-09T00:00:00", id: 2, resource: "R4", text: "Event 2"},
      ];

      const links = [
        {from: 1, to: 2, type: "FinishToStart", color: "#4CAF50"},
      ];

      scheduler.update({resources, events, links});
    },
    init: function() {
      this.loadData();
    },
  };
  app.init();

</script>

</body>
</html>