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

HTML5 JavaScript Gantt Chart - Initialization

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

HTML5 JavaScript Gantt Chart - Loading Tasks from 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

HTML5 JavaScript Gantt Chart - 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

HTML5 JavaScript Gantt Chart - 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

HTML5 JavaScript Gantt Chart - 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

HTML5 JavaScript Gantt Chart - 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)

HTML5 JavaScript Gantt Chart - Delete Links using  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:

HTML5 JavaScript Gantt Chart - Highlighting Links on Hover

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)

HTML5 JavaScript Gantt Chart - Task Progress Bar

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

HTML5 JavaScript Gantt Chart - Columns with Additional Data

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

HTML5 JavaScript Gantt Chart - 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