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 AJAX updates
  • Includes a trial version of DayPilot Pro for JavaScript
  • Supports SQLite and MySQL as the database backend

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-gantt-chart-initialization.png

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> that will serve as a placeholder for the HTML5 Gantt chart control:

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

Configure the Gantt chart using the following initialization JavaScript code:

<script type="text/javascript">
  var dp = new DayPilot.Gantt("dp");
  dp.startDate = "2015-01-01";
  dp.days = 31;
  dp.init();
</script>

This configuration will display an empty Gantt chart for January 2015 without any tasks.

Loading Gantt Chart Tasks from the Database

html5-gantt-chart-loading-tasks.png

We will use jQuery to load the task data from the server side.

<script type="text/javascript">
  var dp = new DayPilot.Gantt("dp");
  dp.startDate = "2015-01-01";
  dp.days = 31;
  dp.init();

  loadTasks();

  function loadTasks() {
      $.post("backend_tasks.php", function(data) {
          dp.tasks.list = data;
          dp.update();
      });
  }

</script>

This tutorial uses a simple SQLite database. The tasks are stored in a [task] table with the following structure.

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
);

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 "backend_tasks.php" PHP script on the server side. We will start with a simple query:

<?php
require_once '_db.php';

$stmt = $db->prepare('SELECT * FROM task');
$stmt->execute();
$items = $stmt->fetchAll();    

class Task {}

$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:

[
  {
    "id":"1",
    "text":"Task 1",
    "start":"2015-01-25T00:00:00",
    "end":"2015-01-26T10:00:00",
  },
  {
    "id":"2",
    "text":"Task 2",
    "start":"2015-01-01T00:00:00",
    "end":"2015-01-02T00:00:00",
  },
  {
    "id":"3",
    "text":"Task 3",
    "start":"2015-01-01T00:00:00",
    "end":"2015-01-02T00:00:00"
  }
]

Loading Task Hierarchy

html5-gantt-chart-task-hierarchy.png

In order to display the tasks in a tree we need to update the backend_tasks.php script. The task children will be returned in a "children" property.

<?php
require_once '_db.php';

class Task {}

$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 = $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:

[
  {
    "id":"1",
    "text":"Task 1",
    "start":"2015-01-02T00:00:00",
    "end":"2015-01-04T00:00:00"
  },
  {
    "id":"6",
    "text":"Milestone 1",
    "start":"2015-01-04",
    "end":"2015-01-06"
  },
  {
    "id":"3",
    "text":"Task 3",
    "start":"2015-01-01",
    "end":"2015-01-02"
    "children":[
      {
        "id":"5",
        "text":"Sub-Task 3.1",
        "start":"2015-01-06",
        "end":"2015-01-11"
      }
    ]
  }
]

Task Creating

html5-gantt-chart-task-creating.png

DayPilot Gantt chart control support inline task creating.

We need to enable the feature using .rowCreateHandling property:

dp.rowCreateHandling = "Enabled";

And we will add an event handler that will update the database using an AJAX call:

dp.onRowCreate = function(args) {
  $.post("backend_create.php", {
      name: args.text,
      start: dp.startDate.toString(),
      end: dp.startDate.addDays(1).toString()
  },
  function(data) {
      loadTasks();
  });

};

The "backend_create.php" script creates a new record in the database:

<?php
require_once '_db.php';

$stmt = $db->prepare("INSERT INTO [task] (name, start, end) VALUES (:name, :start, :end)");
$stmt->bindParam(':name', $_POST['name']);
$stmt->bindParam(':start', $_POST['start']);
$stmt->bindParam(':end', $_POST['end']);
$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);

?>

Loading Task Links

html5-gantt-chart-task-links.png

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

function loadLinks() {
  $.post("backend_links.php", function(data) {
      dp.links.list = data;
      dp.update();
  });
}

The "backend_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 {}

$result = array();

foreach($items as $item) {
    $r = new Link();
    $r->id = $item['id'];
    $r->from = $item['from_id'];
    $r->to = $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      INTEGER       PRIMARY KEY AUTOINCREMENT,
    from_id INTEGER       NOT NULL,
    to_id   INTEGER       NOT NULL,
    type    VARCHAR (100) NOT NULL
);

The [from_id] and [to_id] fields store IDs of the source and target tasks. The [type] field stores the link type:

  • "FinishToStart"
  • "FinishToFinish"
  • "StartToStart"
  • "StartToFinish"

Link Creating

html5-gantt-chart-drag-and-drop-link-creating.png

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:

dp.onLinkCreate = function(args) {
  $.post("backend_link_create.php", {
      from: args.from,
      to: args.to,
      type: args.type
  },
  function(data) {
      loadLinks();
  });
};

The event handler fires an AJAX callback to "backend_link_create.php":

<?php
require_once '_db.php';

$stmt = $db->prepare("INSERT INTO [link] (from_id, to_id, type) VALUES (:from, :to, :type)");
$stmt->bindParam(':from', $_POST['from']);
$stmt->bindParam(':to', $_POST['to']);
$stmt->bindParam(':type', $_POST['type']);
$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);

?>

Link Deleting (Context Menu)

html5-gantt-chart-javascript-link-context-menu.png

Users of our Gantt chart application will 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-gantt-chart-javascript-link-deleting.png

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 AJAX call to "backend_link_delete.php" script.

dp.contextMenuLink = new DayPilot.Menu([
    {
        text: "Delete",
        onclick: function() {
            var link = this.source;
            $.post("backend_link_delete.php", {
                id: link.id()
            },
            function(data) {
                loadLinks();
            });
        }
    }
]);

backend_link_delete.php

<?php
require_once '_db.php';

$stmt = $db->prepare("DELETE FROM link WHERE id = :id");
$stmt->bindParam(':id', $_POST['id']);
$stmt->execute();

class Result {}

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

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

?>

Progress Bar (Percent Complete)

html5-gantt-chart-progress-bar.png

Each task can display a progress bar and a "percent complete" status. The progress bar is enabled by default.

We will modify the "backend_tasks.php" script to supply the "complete" value for each task:

<?php
require_once '_db.php';

class Task {}

$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'];
      
      $parent = $r->id;
      
      $children = db_get_tasks($parent);
      
      if (!empty($children)) {
          $r->children = tasklist($db, $children);
      }

      $result[] = $r;
    }
    return $result;
}
?>

Gantt Chart Columns

html5-gantt-chart-columns.png

We also want to display additional data for each task. The columns in the row header are customizable using .columns property:

dp.columns = [
  { title: "Name", property: "text", width: 100},
  { title: "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.

dp.onBeforeRowHeaderRender = function(args) {
  args.row.columns[1].html = new DayPilot.TimeSpan(args.task.end().getTime() - args.task.start().getTime()).toString("d") + " days";
};

Milestones

html5-gantt-chart-milestones.png

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 - just the task type is set to "Milestone".

PHP

<?php
require_once '_db.php';

class Task {}

$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:

[
  {
    "id":"1",
    "text":"Task 1",
    "start":"2015-01-02T00:00:00",
    "end":"2015-01-04T00:00:00",
    "complete":"0"
  },
  {
    "id":"6",
    "text":"Milestone 1",
    "start":"2015-01-04",
    "end":"2015-01-06",
    "complete":"0",
    "type":"Milestone"
  },
  {
    "id":"3",
    "text":"Task 3",
    "start":"2015-01-01",
    "end":"2015-01-02",
    "complete":"0",
    "children":[
      {
        "id":"5",
        "text":"Sub-Task 3.1",
        "start":"2015-01-06",
        "end":"2015-01-11",
        "complete":"50"
      }
    ]
  }
]

Database Schema (SQLite)

The database access code and configuration is stored in _db.php file. By default, it uses 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 copy _db_mysql.php over the current _db.php file.
  • 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 beginnin of _db_mysql.php for $server, $port, $username and $password variables).

The MySQL database is initialized with the following DDL schema:

CREATE TABLE task (
    id               INTEGER  PRIMARY KEY  AUTO_INCREMENT,
    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 AUTO_INCREMENT,
    from_id INTEGER       NOT NULL,
    to_id   INTEGER       NOT NULL,
    type    VARCHAR (100) NOT NULL
);

The default database connection configuration (_db_mysql.php):

<?php

$host = "127.0.0.1";
$port = 3306;
$username = "username";
$password = "password";
$database = "gantt";

// ...