Features
This tutorial shows how to create a HTML5 Gantt chart in a simple PHP/Javascript web application. It uses the Gantt chart control from DayPilot Pro for JavaScript library. It loads data from a sample SQLite database. Supports drag and drop, task editing using a modal dialog, inline task creating, task groups and milestones. PHP source code of the application is included.
HTML5 Gantt chart
CSS3 theme
Inline task creating
Drag and drop task moving
Drag and drop task resizing
Task hierarchy (task groups)
Task progress bar (percent complete)
Custom columns with additional information (task name, duration)
Milestone support
Task editing using a modal dialog
Fast updates using REST API
Supports SQLite and MySQL as the database backend
To start quickly with your own application, you can use the online DayPilot UI Builder app - it let’s you configure the Gantt Chart visually and generate a downloadable project.
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.
Initial Setup of the Gantt Chart
Create an empty HTML5 page and include DayPilot Pro for JavaScript [download] package:
<script src="js/daypilot/daypilot-all.min.js" type="text/javascript"></script>
Insert an empty <div>
element that will serve as a placeholder for the HTML5 Gantt chart control:
<div id="gantt"></div>
Configure the JavaScript Gantt chart using the following initialization code:
<script>
const gantt = new DayPilot.Gantt("gantt", {
startDate: "2025-01-01",
days: 31,
});
gantt.init();
</script>
This configuration will display an empty Gantt chart for January 2025 without any tasks.
Loading Gantt Chart Tasks from the Database
We will use the built-in tasks.load()
method to load the task data from the server side. The loadTasks()
function loads the data using a HTTP get request to gantt_tasks.php
endpoint which returns the Gantt Chart task data in JSON format.
<script type="text/javascript">
const gantt = new DayPilot.Gantt("gantt", {
startDate: "2025-01-01",
days: 31,
});
gantt.init();
const app = {
loadTasks() {
gantt.tasks.load("gantt_tasks.php");
}
};
app.loadTasks();
</script>
This tutorial uses a simple MySQL database. The tasks are stored in the task
table with the following structure.
CREATE TABLE `task` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` TEXT NULL,
`start` DATETIME NULL DEFAULT NULL,
`end` DATETIME NULL DEFAULT NULL,
`parent_id` INT(11) NULL DEFAULT NULL,
`milestone` TINYINT(1) NOT NULL DEFAULT '0',
`ordinal` INT(11) NULL DEFAULT NULL,
`ordinal_priority` DATETIME NULL DEFAULT NULL,
`complete` INT(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
For the basic setup, only the following fields are required:
id
(primary key)name
(task name)start
(task start date/time)end
(task end date/time)
We use a few other fields that we will use to enable additional features:
1. In order to display the tasks in a hierarchy, it is necessary to add the parent id reference:
parent_id
2. In order to store custom task order and support drag and drop re-ordering we need two more fields:
ordinal
ordinal_priority
3. If you want to support milestones, you need the boolean milestone field:
milestone
4., You also need a special field to store information about the task progress:
complete
The loadTasks()
JavaScript method calls the gantt_tasks.php
PHP script on the server side. This script loads the tasks data from the MySQL database:
<?php
require_once '_db.php';
$stmt = $db->prepare('SELECT * FROM task');
$stmt->execute();
$items = $stmt->fetchAll();
class Task {
public $id;
public $text;
public $start;
public $end;
}
$result = array();
foreach($items as $item) {
$r = new Task();
// rows
$r->id = $item['id'];
$r->text = $item['name'];
$r->start = $item['start'];
$r->end = $item['end'];
$result[] = $r;
}
header('Content-Type: application/json');
echo json_encode($result);
It returns the Gantt chart tasks as a JSON array. Sample JSON response:
[
{
"text": "Task 1",
"start": "2025-01-02",
"end": "2025-01-05",
"id": 1
},
{
"text": "Task 2",
"start": "2025-01-03",
"end": "2025-01-08",
"id": 2
},
{
"text": "Task 3",
"start": "2025-01-04",
"end": "2025-01-15",
"id": 3
}
]
Loading Task Hierarchy
In order to display the tasks in a tree we need to update the gantt_tasks.php
script. The child tasks will be returned in the children
property.
<?php
require_once '_db.php';
class Task {
public $id;
public $text;
public $start;
public $end;
public $children;
}
$result = tasklist($db, db_get_tasks(null));
header('Content-Type: application/json');
echo json_encode($result);
function tasklist($db, $items) {
$result = array();
foreach($items as $item) {
$r = new Task();
// rows
$r->id = (int) $item['id'];
$r->text = htmlspecialchars($item['name']);
$r->start = $item['start'];
$r->end = $item['end'];
$parent = $r->id;
$children = db_get_tasks($parent);
if (!empty($children)) {
$r->children = tasklist($db, $children);
}
$result[] = $r;
}
return $result;
}
The db_get_tasks()
function is defined in _db.php
:
function db_get_tasks($parent) {
global $db;
$str = 'SELECT * FROM task WHERE parent_id = :parent ORDER BY ordinal, ordinal_priority desc';
if ($parent == null) {
$str = str_replace("= :parent", "is null", $str);
$stmt = $db->prepare($str);
}
else {
$stmt = $db->prepare($str);
$stmt->bindParam(':parent', $parent);
}
$stmt->execute();
return $stmt->fetchAll();
}
Sample JSON data:
[
{
"text": "Task 1",
"start": "2025-01-02",
"end": "2025-01-05",
"id": 1
},
{
"text": "Task 2",
"start": "2025-01-03",
"end": "2025-01-08",
"id": 2
},
{
"text": "Task 3",
"start": "2025-01-04T00:00:00",
"end": "2025-01-15T00:00:00",
"id": 3,
"children": [
{
"text": "Task 3.1",
"start": "2025-01-04",
"end": "2025-01-15",
"id": 4
}
]
}
]
Task Creation
The JavaScript Gantt chart control supports inline task creation. We need to enable the feature using rowCreateHandling
property:
const gantt = new DayPilot.Gantt("gantt", {
rowCreateHandling: "Enabled",
// ...
});
And we will add an event handler that will update the database using an HTTP call:
const gantt = new DayPilot.Gantt("gantt", {
rowCreateHandling: "Enabled",
onRowCreate: async (args) => {
const data = {
name: args.text,
start: gantt.startDate,
end: gantt.startDate.addDays(1)
};
await DayPilot.Http.post("gantt_task_create.php", data);
app.loadTasks();
},
// ...
});
The gantt_task_create.php
script creates a new record in the MySQL database and returns the auto-generated ID (primary key):
<?php
require_once '_db.php';
$json = file_get_contents('php://input');
$params = json_decode($json);
$now = (new DateTime("now"))->format('Y-m-d H:i:s');
$ordinal = db_get_max_ordinal(null) + 1;
$stmt = $db->prepare("INSERT INTO task (name, start, end, ordinal, ordinal_priority) VALUES (:name, :start, :end, :ordinal, :priority)");
$stmt->bindParam(':name', $params->name);
$stmt->bindParam(':start', $params->start);
$stmt->bindParam(':end', $params->end);
$stmt->bindParam(":ordinal", $ordinal);
$stmt->bindParam(":priority", $now);
$stmt->execute();
class Result {
public $id;
public $result;
public $message;
}
$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);
Loading Task Links
The Gantt chart supports task dependencies that will be displayed as links connecting the task boxes. The task links are enabled by default, we just need to load the link data:
JavaScript
const app = {
// ...
loadLinks() {
gantt.links.load("gantt_links.php");
}
};
The gantt_links.php
PHP script loads the links from the link
database table.
<?php
require_once '_db.php';
$stmt = $db->prepare("SELECT * FROM link");
$stmt->execute();
$items = $stmt->fetchAll();
class Link {
public $id;
public $from;
public $to;
public $type;
}
$result = array();
foreach($items as $item) {
$r = new Link();
$r->id = (int) $item['id'];
$r->from = (int) $item['from_id'];
$r->to = (int) $item['to_id'];
$r->type = $item['type'];
$result[] = $r;
}
header('Content-Type: application/json');
echo json_encode($result);
The link
table has the following structure:
CREATE TABLE `link` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`from_id` INT(11) NOT NULL,
`to_id` INT(11) NOT NULL,
`type` VARCHAR(100) NOT NULL,
PRIMARY KEY (`id`)
);
The from_id
and to_id
fields store IDs of the source and target tasks. The type
field stores the link type - one of these string values:
"FinishToStart"
"FinishToFinish"
"StartToStart"
"StartToFinish"
Sample JSON response with Gantt chart link data:
[
{
"from": 1,
"to": 2,
"type": "FinishToStart"
}
]
Link Creation
We will let the users create new links in the Gantt chart using drag and drop. This feature is enable by default. As soon as the user selects the source and target tasks, onLinkCreate
event will be fired:
const gantt = new DayPilot.Gantt("gantt", {
// ...
onLinkCreate: async (args) => {
const data = {
from: args.from,
to: args.to,
type: args.type
};
await DayPilot.Http.post("gantt_link_create.php", data);
app.loadLinks();
},
// ...
});
The event handler fires an HTTP request to gantt_link_create.php
:
<?php
require_once '_db.php';
$json = file_get_contents('php://input');
$params = json_decode($json);
$stmt = $db->prepare("INSERT INTO link (from_id, to_id, type) VALUES (:from, :to, :type)");
$stmt->bindParam(':from', $params->from);
$stmt->bindParam(':to', $params->to);
$stmt->bindParam(':type', $params->type);
$stmt->execute();
class Result {
public $id;
public $result;
public $message;
}
$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);
The PHP script returns the ID of the newly-created record on success.
Link Deleting (Context Menu)
Users of our Gantt chart application should be able to delete task links using a context menu.
The default CSS theme highlights the links on hover - this indicates it is an active element and it's easier to select a particular link:
We will add a context menu using contextMenuLink
property.
The context menu has a single "Delete" item.
Clicking the "Delete" menu item immediately deletes the link using an HTTP POST request to
gantt_link_delete.php
script.
const gantt = new DayPilot.Gantt("gantt", {
// ...
contextMenuLink: new DayPilot.Menu([
{
text: "Delete",
onClick: async (args) => {
const id = args.source.id();
await DayPilot.Http.post("gantt_link_delete.php", { id });
app.loadLinks();
}
}
]),
// ...
});
An this is the gantt_link_delete.php
script:
<?php
require_once '_db.php';
$json = file_get_contents('php://input');
$params = json_decode($json);
$stmt = $db->prepare("DELETE FROM link WHERE id = :id");
$stmt->bindParam(':id', $params->id);
$stmt->execute();
class Result {
public $result;
}
$response = new Result();
$response->result = 'OK';
header('Content-Type: application/json');
echo json_encode($response);
Task Progress Bar (Percent Complete)
Each task can display a progress bar and a "percent complete" status. The progress bar is enabled by default.
We will modify the gantt_tasks.php
script to include the complete
value in the task data item:
<?php
require_once '_db.php';
class Task {
public $id;
public $text;
public $start;
public $end;
public $complete;
public $children;
}
$result = tasklist($db, db_get_tasks(null));
header('Content-Type: application/json');
echo json_encode($result);
function tasklist($db, $items) {
$result = array();
foreach($items as $item) {
$r = new Task();
// rows
$r->id = $item['id'];
$r->text = htmlspecialchars($item['name']);
$r->start = $item['start'];
$r->end = $item['end'];
$r->complete = intval($item['complete']);
$parent = $r->id;
$children = db_get_tasks($parent);
if (!empty($children)) {
$r->children = tasklist($db, $children);
}
$result[] = $r;
}
return $result;
}
Gantt Chart Columns with Additional Information
We also want to display additional data for each task. The columns in the row header are customizable using columns property:
const gantt = new DayPilot.Gantt("gantt", {
// ...
columns: [
{ name: "Name", display: "text", width: 100},
{ name: "Duration", width: 100}
],
// ...
});
The column data can be loaded automatically from the specified property of the task data object (property
).
You can also provide custom HTML using onBeforeRowHeaderRender event - we will use this event to display the task duration in the second column of the Gantt chart.
const gantt = new DayPilot.Gantt("gantt", {
// ...
onBeforeRowHeaderRender: (args) => {
args.row.columns[1].html = args.task.duration().toString("d") + " days";
},
// ...
});
Milestones
The Gantt chart control includes support for milestones - fixed points in time that have no duration specified. The milestones are loaded as part of the task hierarchy. The milestone data item looks like a regular task but its type is set to "Milestone"
.
This is the extended version of gantt_tasks.php
which includes milestone
property:
<?php
require_once '_db.php';
class Task {
public $id;
public $text;
public $start;
public $end;
public $complete;
public $type;
public $children;
}
$result = tasklist($db, db_get_tasks(null));
header('Content-Type: application/json');
echo json_encode($result);
function tasklist($db, $items) {
$result = array();
foreach($items as $item) {
$r = new Task();
// rows
$r->id = $item['id'];
$r->text = htmlspecialchars($item['name']);
$r->start = $item['start'];
$r->end = $item['end'];
$r->complete = $item['complete'];
if ($item['milestone']) {
$r->type = 'Milestone';
}
$parent = $r->id;
$children = db_get_tasks($parent);
if (!empty($children)) {
$r->children = tasklist($db, $children);
}
$result[] = $r;
}
return $result;
}
Sample JSON result:
[
{
"text": "Task 1",
"start": "2025-01-02",
"end": "2025-01-05",
"id": 1
},
{
"text": "Milestone 1",
"start": "2025-01-03",
"end": "2025-01-03",
"id": 2,
"type": "Milestone"
},
{
"text": "Task 3",
"start": "2025-01-04T00:00:00",
"end": "2025-01-15T00:00:00",
"id": 3,
"children": [
{
"text": "Task 3.1",
"start": "2025-01-04",
"end": "2025-01-15",
"id": 4
}
]
}
]
Database Schema (SQLite)
The database access code and configuration is stored in _db.php
file. By default, it uses an SQLite database.
The data is stored in
daypilot.sqlite
file in the application root.If the database file doesn't exists, it will be created and initialized automatically (make sure that the application has write permission to the directory).
The default SQLite configuration is stored in
_db_sqlite.php
.
The SQLite database is initialized with the following DDL schema:
CREATE TABLE task (
id INTEGER PRIMARY KEY,
name TEXT,
start DATETIME,
[end] DATETIME,
parent_id INTEGER,
milestone BOOLEAN DEFAULT (0) NOT NULL,
ordinal INTEGER,
ordinal_priority DATETIME,
complete INTEGER DEFAULT (0) NOT NULL
);
CREATE TABLE link (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_id INTEGER NOT NULL,
to_id INTEGER NOT NULL,
type VARCHAR (100) NOT NULL
);
Database Schema (MySQL)
The MySQL database configuration is stored in _db_mysql.php
in the application root.
If you want to use MySQL database with the Gantt chart sample instead of the default SQLite, just edit
_db.php
as described below.The
_db_mysql.php
file will automatically create and initialize the database if it doesn't exist.Make sure that the database connection is configured properly (check the beginning of
_db_mysql.php
for$server
,$port
,$username
and$password
variables).
Edit _db.php
as follows to switch to MySQL:
<?php
// use sqlite
//require_once '_db_sqlite.php';
// use MySQL
require_once '_db_mysql.php';
The MySQL database is initialized with the following DDL schema:
CREATE TABLE `task` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` TEXT NULL,
`start` DATETIME NULL DEFAULT NULL,
`end` DATETIME NULL DEFAULT NULL,
`parent_id` INT(11) NULL DEFAULT NULL,
`milestone` TINYINT(1) NOT NULL DEFAULT '0',
`ordinal` INT(11) NULL DEFAULT NULL,
`ordinal_priority` DATETIME NULL DEFAULT NULL,
`complete` INT(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
CREATE TABLE `link` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`from_id` INT(11) NOT NULL,
`to_id` INT(11) NOT NULL,
`type` VARCHAR(100) NOT NULL,
PRIMARY KEY (`id`)
);
The default database connection configuration (_db_mysql.php
):
<?php
$host = "127.0.0.1";
$port = 3306;
$username = "username";
$password = "password";
$database = "gantt";
// ...
History
July 27, 2024: Upgraded to DayPilot Pro for JavaScript 2024.3. PHP 8+ compatibility.
June 10, 2021: Upgraded to DayPilot Pro for JavaScript 2021.2
November 24, 2020: Upgraded to DayPilot Pro for JavaScript 2020.4
August 8, 2020: Upgraded to DayPilot Pro for JavaScript 2020.3, jQuery dependency removed.
September 16, 2019: Upgraded to DayPilot Pro for JavaScript 2019.3.4012, cleanup.
June 27, 2017: Upgraded to DayPilot Pro for JavaScript 8.4.2911
June 29, 2016: Latest DayPilot Pro trial version, MySQL support added.
February 6, 2015: Initial release