This tutorial shows how to build a HTML5 scheduler application. It displays multiple resources (grouped by category), with support for drag and drop operations (moving, resizing).

It uses DayPilot HTML5 Scheduler JavaScript library.

Features

  • HTML5 scheduler displaying a monthly timeline

  • Pure JavaScript solution with no third party libraries required

  • Sample PHP backend with MySQL/SQLite database

  • Resources grouped by categories (People, Tools)

  • Custom time header formatting

  • Drag and drop event creating, moving and resizing

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.

Scheduler Initialization

html5-scheduler-javascript-initialization.png

Include the JavaScript library:

<script src="js/daypilot/daypilot-all.min.js" type="text/javascript"></script>

Add a placeholder div to the HTML5 page:

<div id="dp"></div>

Add the initialization JavaScript code:

<script type="text/javascript">
  var dp = new DayPilot.Scheduler("dp");
  dp.init();
</script>

Loading HTML5 Scheduler Resources

html5-scheduler-javascript-loading-resources.png

We will load the scheduler resources using a simple loadResources() method.

JavaScript

function loadResources() {
  dp.rows.load("backend_resources.php");
}

It uses the rows.load() method to request the resource data from the server-side backend_resources.php page.

We are loading the resources after the scheduler initialization (after init() method is called).

JavaScript

<script type="text/javascript">

  var dp = new DayPilot.Scheduler("dp");
  dp.init();

  loadResources();


</script>

You can also fill the resources array during initialization.

Sample JSON data (received from backend_resources.php):

[
  {"id":"1","name":"Person 1"},
  {"id":"2","name":"Person 2"},
  {"id":"3","name":"Person 3"},
  {"id":"4","name":"Person 4"},
  {"id":"5","name":"Tool 1"},
  {"id":"6","name":"Tool 2"},
  {"id":"7","name":"Tool 3"},
  {"id":"8","name":"Tool 4"}
]

Scheduler Resource Tree

html5-scheduler-javascript-resource-tree.png

We will enable the resource tree using treeEnabled property.

JavaScript

<script type="text/javascript">
  var dp = new DayPilot.Scheduler("dp");
  dp.treeEnabled = true;
  dp.init();
  
  loadResources();
</script>

It is necessary to modify the resources array to include the resource parents and children.

Sample JSON data:

[
  {"id":"group_1","name":"People","expanded":true,"children":
    [
      {"id":"1","name":"Person 1"},
      {"id":"2","name":"Person 2"},
      {"id":"3","name":"Person 3"},
      {"id":"4","name":"Person 4"}
    ]
  },
  {"id":"group_2","name":"Tools","expanded":true,"children":
    [
      {"id":"5","name":"Tool 1"},
      {"id":"6","name":"Tool 2"},
      {"id":"7","name":"Tool 3"},
      {"id":"8","name":"Tool 4"}
    ]
  }
]

PHP (backend_resources.php)

<?php
require_once '_db.php';
    
$scheduler_groups = $db->query('SELECT * FROM groups ORDER BY name');

class Group {}
class Resource {}

$groups = array();

foreach($scheduler_groups as $group) {
  $g = new Group();
  $g->id = "group_".$group['id'];
  $g->name = $group['name'];
  $g->expanded = true;
  $g->children = array();
  $groups[] = $g;
  
  $stmt = $db->prepare('SELECT * FROM resources WHERE group_id = :group ORDER BY name');
  $stmt->bindParam(':group', $group['id']);
  $stmt->execute();
  $scheduler_resources = $stmt->fetchAll();  
  
  foreach($scheduler_resources as $resource) {
    $r = new Resource();
    $r->id = $resource['id'];
    $r->name = $resource['name'];
    $g->children[] = $r;
  }
}

header('Content-Type: application/json');
echo json_encode($groups);

Scheduler Scale (Grid Cell Duration)

html5-scheduler-javascript-scale.png

The default scheduler scale is set to display 1 day, divided to 24 one-hour cells. We will set the scale (cell duration) to 1 day and display the current month.

JavaScript:

// cell duration
dp.scale = "Day";

// display the current month
dp.startDate = new DayPilot.Date().firstDayOfMonth();
dp.days = dp.startDate.daysInMonth();

Other scale values are available in the scheduler:

  • "CellDuration" (custom number of minutes set using cellDuration property)

  • "Manual" (manually created timeline)

  • "Minute"

  • "Hour"

  • "Day"

  • "Week"

  • "Month"

  • "Year"

Scheduler Time Headers

html5-scheduler-javascript-time-headers.png

We will customize the time headers using timeHeaders array.

  • The first scheduler header row will display months (formatted as "July 2020")

  • The second scheduler header row will display days (formatted as "28")

JavaScript:

dp.timeHeaders = [
  { groupBy: "Month", format: "MMMM yyyy" },
  { groupBy: "Day", format: "d"}
];

Horizontal Scrollbar (Automatic Width)

html5-scheduler-javascript-horizontal-scrollbar.png

The scheduler displays time cells width fixed width (40 pixels) in the default configuration.

We want to get rid of the horizontal scrollbar. We will use cellWidthSpec property to adjust the cell width automatically to match the total control width (100% of the HTML5 page).

dp.cellWidthSpec = "Auto";

The cell width is adjusted automatically on window resize.

html5-scheduler-javascript-auto-cell-width.png

Loading Events

html5-scheduler-events.png

We will load the scheduler data (events) using events.load() method in loadEvents().

function loadEvents() {
  dp.events.load("backend_events.php");
}

The events.load() method calls the specified URL with start and end query string parameters holding the start and end of the current Scheduler grid view.

Sample JSON data:

[{
  "id": "12",
  "text": "Reservation",
  "start": "2020-07-03T00:00:00",
  "end": "2020-07-10T00:00:00",
  "resource": "5"
}, {
  "id": "13",
  "text": "Meeting",
  "start": "2020-07-06T00:00:00",
  "end": "2020-07-10T00:00:00",
  "resource": "2"
}]

PHP (backend_events.php)

<?php
require_once '_db.php';
    
$stmt = $db->prepare('SELECT * FROM events WHERE NOT ((end <= :start) OR (start >= :end))');
$stmt->bindParam(':start', $_GET['start']);
$stmt->bindParam(':end', $_GET['end']);
$stmt->execute();
$result = $stmt->fetchAll();

class Event {}
$events = array();

foreach($result as $row) {
  $e = new Event();
  $e->id = $row['id'];
  $e->text = $row['name'];
  $e->start = $row['start'];
  $e->end = $row['end'];
  $e->resource = $row['resource_id'];
  $events[] = $e;
}

header('Content-Type: application/json');
echo json_encode($events);

Displaying Event Details on Hover

html5-scheduler-javascript-event-details-bubble.png

It is possible to display a box with custom HTML data on hover using DayPilot.Bubble. The bubble property is initialized with a standard DayPilot.Bubble instance by default:

dp.bubble = new DayPilot.Bubble();

The easiest way to specify the bubble HTML is to add it to the event data (returned by backend_events.php). You can also add it on the client side using onBeforeEventRender event handler.

[{
  "id": "12",
  "text": "Reservation",
  "start": "2020-07-03T00:00:00",
  "end": "2020-07-10T00:00:00",
  "resource": "5",
  "bubbleHtml": "Event details: <br\/>Reservation"
}, {
  "id": "13",
  "text": "Meeting",
  "start": "2020-07-06T00:00:00",
  "end": "2020-07-10T00:00:00",
  "resource": "2",
  "bubbleHtml": "Event details: <br\/>Meeting"
}]

Drag and Drop Event Moving

html5-scheduler-javascript-drag-and-drop-event-moving.png

Drag and drop is enabled by default in the scheduler.

We will use onEventMoved handler to notify the server backend about the change:

dp.onEventMoved = function (args) {
  DayPilot.Http.ajax({
    url: "backend_move.php",
    data: {
      id: args.e.id(),
      newStart: args.newStart.toString(),
      newEnd: args.newEnd.toString(),
      newResource: args.newResource
    },
    success: function () {
      dp.message("Moved.");
    }
  });
};

PHP (backend_move.php)

<?php
require_once '_db.php';

$json = file_get_contents('php://input');
$params = json_decode($json);

$stmt = $db->prepare("UPDATE events SET start = :start, end = :end, resource_id = :resource WHERE id = :id");
$stmt->bindParam(':id', $params->id);
$stmt->bindParam(':start', $params->newStart);
$stmt->bindParam(':end', $params->newEnd);
$stmt->bindParam(':resource', $params->newResource);
$stmt->execute();

class Result {}

$response = new Result();
$response->result = 'OK';
$response->message = 'Update successful';

header('Content-Type: application/json');
echo json_encode($response);

Drag and Drop Event Resizing

html5-scheduler-javascript-drag-and-drop-event-resizing.png

The event resizing uses an event-handling mechanism similar to event moving.

The onEventResized event is called as soon as the event position is updated. We will use it to update the database using an AJAX call.

dp.onEventResized = function (args) {
  DayPilot.Http.ajax({
    url: "backend_resize.php",
    data: {
      id: args.e.id(),
      newStart: args.newStart.toString(),
      newEnd: args.newEnd.toString()
    },
    success: function () {
      dp.message("Resized.");
    }
  });
};

PHP (backend_resize.php)

<?php
require_once '_db.php';

$json = file_get_contents('php://input');
$params = json_decode($json);

$stmt = $db->prepare("UPDATE events SET start = :start, end = :end WHERE id = :id");
$stmt->bindParam(':id', $params->id);
$stmt->bindParam(':start', $params->newStart);
$stmt->bindParam(':end', $params->newEnd);
$stmt->execute();

class Result {}

$response = new Result();
$response->result = 'OK';
$response->message = 'Update successful';

header('Content-Type: application/json');
echo json_encode($response);

Drag and Drop Event Creating

html5-scheduler-javascript-drag-and-drop-event-creating.png

We will handle drag and drop time range selection to invoke a new event modal dialog and create the event.

There is no default action associated with a time range selecting event so we will need to define the logic.

JavaScript

dp.onTimeRangeSelected = function (args) {
  var form = [
    {name: "Text", id: "text"},
    {name: "Start", id: "start", dateFormat: "M/d/yyyy h:mm:ss tt"},
    {name: "End", id: "end", dateFormat: "M/d/yyyy h:mm:ss tt"},
    {name: "Resource", id: "resource", options: flatten(dp.resources)}
  ];

  var data = {
    start: args.start,
    end: args.end,
    resource: args.resource,
    text: "New event"
  };

  var options = {
    focus: "text"
  };

  DayPilot.Modal.form(form, data, options).then(function(modal) {
    dp.clearSelection();
    if (modal.canceled) {
      return;
    }
    DayPilot.Http.ajax({
      url: "backend_create.php",
      data: modal.result,
      success: function(response) {
        modal.result.id = response.data.id
        dp.events.add(modal.result);
        dp.message("Created.");
      }
    });
  });

};

Our onTimeRangeSelected event handler opens a dynamically-created modal dialog using DayPilot.Modal.form() and creates the new event in a database by calling backend_create.php endpoint.

For more information on using DayPilot.Modal.form() to create dynamic modal dialogs please see JavaScript Scheduler: How to Edit Multiple Fields using a Modal Dialog.

html5-scheduler-javascript-new-event-modal-dialog.png

The server-side handler creates the event in the database and returns a custom message:

html5-scheduler-javascript-event-created-message.png

PHP (backend_create.php)

<?php
require_once '_db.php';

$json = file_get_contents('php://input');
$params = json_decode($json);

$stmt = $db->prepare("INSERT INTO events (name, start, end, resource_id) VALUES (:name, :start, :end, :resource)");
$stmt->bindParam(':start', $params->start);
$stmt->bindParam(':end', $params->end);
$stmt->bindParam(':name', $params->text);
$stmt->bindParam(':resource', $params->resource);
$stmt->execute();

class Result {}

$response = new Result();
$response->result = 'OK';
$response->message = 'Created with id: '.$db->lastInsertId();
$response->id = $db->lastInsertId();

header('Content-Type: application/json');
echo json_encode($response);

Event Editing using a Modal Dialog

html5-scheduler-javascript-edit-event-modal-dialog.png

You can invoke a modal dialog for editing the events details using DayPilot.Modal.form().

Add a new onClickEvent handler to the scheduler:

dp.onEventClick = function (args) {
  var form = [
    {name: "Text", id: "text"},
    {name: "Start", id: "start", dateFormat: "M/d/yyyy h:mm:ss tt"},
    {name: "End", id: "end", dateFormat: "M/d/yyyy h:mm:ss tt"},
    {name: "Resource", id: "resource", options: flatten(dp.resources)}
  ];

  var data = args.e.data;

  var options = {
    focus: "text"
  };

  DayPilot.Modal.form(form, data, options).then(function(modal) {
    dp.clearSelection();
    if (modal.canceled) {
      return;
    }
    DayPilot.Http.ajax({
      url: "backend_update.php",
      data: modal.result,
      success: function(response) {
        console.log("Updating data", modal.result);
        dp.events.update(modal.result);
        dp.message("Updated.");
      }
    });
  });

};

The modal dialog will submit the data using an AJAX call to backend_update.php.

<?php
require_once '_db.php';

$json = file_get_contents('php://input');
$params = json_decode($json);

$stmt = $db->prepare("UPDATE events SET name = :name, start = :start, end = :end, resource_id = :resource WHERE id = :id");
$stmt->bindParam(':id', $params->id);
$stmt->bindParam(':name', $params->text);
$stmt->bindParam(':start', $params->start);
$stmt->bindParam(':end', $params->end);
$stmt->bindParam(':resource', $params->resource);
$stmt->execute();

class Result {}

$response = new Result();
$response->result = 'OK';
$response->message = 'Update successful';

header('Content-Type: application/json');
echo json_encode($response);

Icon for Event Deleting

html5-scheduler-javascript-event-deleting-icon.png

Event deleting using a built-in icon is disabled by default but you can easily enable it:

dp.eventDeleteHandling = "Update";

The Scheduler will fire onEventDelete/onEventDeleted events when a user clicks the icon:

dp.onEventDeleted = function (args) {
  DayPilot.Http.ajax({
    url: "backend_delete.php",
    data: {
      id: args.e.id()
    },
    success: function () {
      dp.message("Deleted.");
    }
  });
};

See also event deleting.

MySQL Database Schema

By default, the PHP project uses SQLite database. The database file (daypilot.sqlite) will be created automatically, but PHP will need write permissions for the project folder. Do not use the SQLite backend in production.

To switch to MySQL database, edit _db.php file to look like this:

<?php

// use sqlite
require_once '_db_sqlite.php';

// use MySQL
//require_once '_db_mysql.php';

This is the DDL of the database schema used in this HTML5 Scheduler project. If you create the database (html5-scheduler) and set the MySQL connection string properties correctly (in _db_mysql.php), the schema will be created automatically.

CREATE TABLE IF NOT EXISTS `events` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` text,
  `start` datetime DEFAULT NULL,
  `end` datetime DEFAULT NULL,
  `resource_id` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE IF NOT EXISTS `groups` (
  `id` int(11) NOT NULL,
  `name` varchar(200) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE IF NOT EXISTS `resources` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(200) DEFAULT NULL,
  `group_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
);

History

  • July 12, 2020: Modal dialogs switched to DayPilot.Modal.form(), jQuery dependency removed, MySQL support added, latest DayPilot Pro version (2020.3.4579)

  • June 27, 2017: Deleting code updated, DB init fixed, latest DayPilot Pro version (8.4.2911)

  • February 28, 2014: Initial release