Features

  • The parent rows of the Scheduler component are used to display availability of child resources.

  • The parent row shows a number of available resources for each time cell.

  • The availability is also highlighted using color bars.

  • Requires DayPilot Pro for JavaScript 2020.4.4722 or later

  • Includes 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.

Live Demo

Calculating Availability of Child Resources

javascript scheduler total availability

We will use onBeforeCellRender event of the JavaScript Scheduler to calculate the available child resources:

onBeforeCellRender: function(args) {
  if (args.cell.isParent) {
    var children = dp.rows.find(args.cell.resource).children();
    var total = children.length;
    var used = children.filter(function(row) { return !!row.events.forRange(args.cell.start, args.cell.end).length; } ).length;
    var available = total - used;

    args.cell.html = "" + available;
  }
}

This event handler finds all child resources for each parent node and looks for events. If there is an event found that overlaps a particular cell the resource is considered unavailable.

As the final step, we display the available resources in the cell using args.cell.html property.

Updating the Cells

The Scheduler only redraws the cells if it is necessary. By default, it also caches the onBeforeCellRender results to improve performance.

In order to force a refresh of the cells in the parent rows, we need to add cellsAutoUpdated: true to the parent nodes:

dp.resources = [
  {name: "Group 1", id: "G1", expanded: true, cellsAutoUpdated: true, children: [
      {name: "Resource 1", id: "R1"},
      {name: "Resource 2", id: "R2"}
  ]},
  {name: "Group 2", id: "G2", expanded: true, cellsAutoUpdated: true, children: [
      {name: "Resource 3", id: "R3"},
      {name: "Resource 4", id: "R4"},
      {name: "Resource 5", id: "R5"}
  ]},
];

This will ensure that the calculations in parent row cells are updated automatically after every drag and drop operation.

Cell Styling

javascript scheduler group availability styling

Let's add some styling now - instead of displaying the number as a plain text we will display it using an active area. This will let us add some CSS styles an position the text in the middle of the cell.

onBeforeCellRender: function(args) {
  if (args.cell.isParent) {
    var children = dp.rows.find(args.cell.resource).children();
    var total = children.length;
    var used = children.filter(function(row) { return !!row.events.forRange(args.cell.start, args.cell.end).length; } ).length;
    var available = total - used;

    var w = dp.cellWidth / total;
    args.cell.areas = [];
    args.cell.areas.push({
      html: "" + available,
      style: "text-align: center; font-size: 12px; font-weight: bold",
      top: 4,
      left: 0,
      right: 0
    });
  }
}

Color Highlighting

javascript scheduler group availability color bars

In order to make the summary easier to read we will use active areas to add color bars that highlight the child resource availability.

We will use light-green bar to indicate an free position, dark-green bar to indicate unavailable position, and orange bars to indicate that there is no available slot.

onBeforeCellRender: function(args) {
  if (args.cell.isParent) {
    var children = dp.rows.find(args.cell.resource).children();
    var total = children.length;
    var used = children.filter(function(row) { return !!row.events.forRange(args.cell.start, args.cell.end).length; } ).length;
    var available = total - used;

    var w = dp.cellWidth / total;
    args.cell.areas = [];
    args.cell.areas.push({
      html: "" + available,
      style: "text-align: center; font-size: 12px; font-weight: bold",
      top: 4,
      left: 0,
      right: 0
    });

    args.cell.backColor = "#d9ead3";
    if (available === 0) {
      args.cell.backColor = "#f9cb9c";
    }

    DayPilot.list.for(total).forEach(function(item, i) {
      var color = "#b6d7a8";
      if (i < used) {
        color = "#6aa84f";
      }
      if (available === 0) {
        color = "#e69138";
      }
      args.cell.areas.push({
        bottom: 0,
        height: 10,
        left: i*w,
        width: w - 1,
        backColor: color
      });
    })
  }
}

Full Source Code

var dp = new DayPilot.Scheduler("dp", {
  timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
  scale: "Day",
  days: 30,
  startDate: "2019-04-01",
  treeEnabled: true,
  treePreventParentUsage: true,
  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
      }));
    });
  },
  onBeforeCellRender: function(args) {
    if (args.cell.isParent) {
      var children = dp.rows.find(args.cell.resource).children();
      var total = children.length;
      var used = children.filter(function(row) { return !!row.events.forRange(args.cell.start, args.cell.end).length; } ).length;
      var available = total - used;

      var w = dp.cellWidth / total;
      args.cell.areas = [];
      args.cell.areas.push({
        html: "" + available,
        style: "text-align: center; font-size: 12px; font-weight: bold",
        top: 4,
        left: 0,
        right: 0
      });

      args.cell.backColor = "#d9ead3";
      if (available === 0) {
        args.cell.backColor = "#f9cb9c";
      }

      DayPilot.list.for(total).forEach(function(item, i) {
        var color = "#b6d7a8";
        if (i < used) {
          color = "#6aa84f";
        }
        if (available === 0) {
          color = "#e69138";
        }
        args.cell.areas.push({
          bottom: 0,
          height: 10,
          left: i*w,
          width: w - 1,
          backColor: color
        });
      })
    }
  }
});