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.
  • 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. Buy a license.

Live Demo

Calculating Availability of Child Resources

javascript-scheduler-total-availability.png

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 keep the calculations up-to-date, we need to disable the caching:

beforeCellRenderCaching: false,

We also need to invalidate the cells in the parent row to force an update after a change is made using drag and drop:

onTimeRangeSelected: function (args) {
  DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
    // ...
    dp.rows.find(args.resource).parent().cells.all().invalidate();
  });
},
onEventMove: function(args) {
  dp.rows.find(args.e.data.resource).parent().cells.all().invalidate();
  dp.rows.find(args.newResource).parent().cells.all().invalidate();
},
onEventResize: function(args) {
  dp.rows.find(args.e.data.resource).parent().cells.all().invalidate();
},

Cell Styling

javascript-scheduler-group-availability-styling.png

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.png

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
      }));
      dp.rows.find(args.resource).parent().cells.all().invalidate();
    });
  },
  onEventMove: function(args) {
    dp.rows.find(args.e.data.resource).parent().cells.all().invalidate();
    dp.rows.find(args.newResource).parent().cells.all().invalidate();
  },
  onEventResize:function(args) {
    dp.rows.find(args.e.data.resource).parent().cells.all().invalidate();
  },
  beforeCellRenderCaching: false,
  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
        });
      })
    }
  }
});