Features
This tutorial shows how to use DayPilot ASP.NET MVC 5 Gantt chart control.
Hierarchy of tasks
Task groups
Task dependencies
Drag and drop support: task moving and resizing, reordering tasks in the hierarchy, link creating
Adding a new task using a special row
Custom columns with additional data (name, id, duration)
Visual Studio solution
SQL Server database
ASP.NET MVC 5
C# source code
VB.NET source code
License
Licensed for testing and evaluation purposes. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for ASP.NET MVC.
Gantt Initialization: MVC View
Include the client-side DayPilot library in the view (daypilot-all.min.js):
<script src="@Url.Content("~/Scripts/DayPilot/daypilot-all.min.js")" type="text/javascript"></script>
Add the Gantt Chart to the MVC view using DayPilotGantt Html helper:
Index.cshtml
@using DayPilot.Web.Mvc;
@Html.DayPilotGantt("Gantt", new DayPilotGanttConfig
{
BackendUrl = Url.Action("Backend", "Gantt")
})
Index.vbhtml
@imports DayPilot.Web.Mvc
@Html.DayPilotGantt("Gantt", New DayPilotGanttConfig With
{
.BackendUrl = Url.Action("Backend", "Gantt")
})
The BackendUrl property is required. It points to the MVC controller that will handle the AJAX requests.
Gantt Initialization: MVC Controller
Now we will create a new controller for handling the AJAX events.
The controller will contain "Backend" action:
C#
public class GanttController : Controller
{
public ActionResult Backend()
{
// ...
}
}
VB
Public Class GanttController
Inherits Controller
Public Function Backend() As ActionResult
' ...
End Function
End Class
We will create a new class called "Gantt" derived from DayPilotGantt. This class will transform the incoming AJAX requests into server-side events.
C#
public class GanttController : Controller
{
public ActionResult Backend()
{
return new Gantt().CallBack(this);
}
class Gantt : DayPilotGantt
{
}
}
VB
Public Class GanttController
Inherits Controller
Public Function Backend() As ActionResult
Return (New Gantt()).CallBack(Me)
End Function
Private Class Gantt
Inherits DayPilotGantt
End Class
End Class
If you run your ASP.NET MVC project now it will display a blank Gantt chart with no tasks and no timeline:
No we will use the controller to configure the Gantt chart timeline.
We will add an event handler for the "Init" event. This event is called right after the Gantt chart initialization on the client side. It will let us load the Gantt chart data and adjust the configuration.
First, we set the start date to October 1, 2014 and time range to 60 days.
The UpdateWithMessage() call sends the changes to the client side and displays a message ("Welcome!") using the built-in message bar.
C#
class Gantt : DayPilotGantt
{
protected override void OnInit(InitArgs e)
{
StartDate = new DateTime(2014, 10, 1);
Days = 60;
UpdateWithMessage("Welcome!");
}
}
VB
Private Class Gantt
Inherits DayPilotGantt
Protected Overrides Sub OnInit(ByVal e As InitArgs)
StartDate = New Date(2014, 10, 1)
Days = 60
UpdateWithMessage("Welcome!")
End Sub
End Class
This is what you will see with this configuration:
Loading Gantt Tasks
Now we will add task loading code to the OnInit event handler.
C#
class Gantt : DayPilotGantt
{
protected override void OnInit(InitArgs e)
{
StartDate = new DateTime(2014, 10, 1);
Days = 60;
LoadTasks();
UpdateWithMessage("Welcome!");
}
private void LoadTasks()
{
TasksFromEnumerable(
Db.GetTasksFlat().Rows,
new TaskFieldMappings()
{
TextField = "name",
IdField = "id",
StartField = "start",
EndField = "end",
ParentField = "parent_id",
MilestoneField = "milestone",
TagFields = "id"
}
);
}
}
VB
Public Class GanttController
Inherits Controller
Public Function Backend() As ActionResult
Return (New Gantt()).CallBack(Me)
End Function
Private Class Gantt
Inherits DayPilotGantt
Protected Overrides Sub OnInit(ByVal e As InitArgs)
StartDate = New Date(2014, 10, 1)
Days = 60
LoadTasks()
UpdateWithMessage("Welcome!")
End Sub
Private Sub LoadTasks()
TasksFromEnumerable(Db.GetTasksFlat().Rows, New TaskFieldMappings() With {
.TextField = "name",
.IdField = "id",
.StartField = "start",
.EndField = "end",
.ParentField = "parent_id",
.MilestoneField = "milestone",
.TagFields = "id"})
End Sub
End Class
End Class
The tasks are stored in a single database table with the following structure:
Table DDL:
CREATE TABLE [dbo].[task] (
[id] INT IDENTITY (1, 1) NOT NULL,
[name] VARCHAR (50) NULL,
[start] DATETIME NOT NULL,
[end] DATETIME NOT NULL,
[parent_id] INT NULL,
[ordinal] INT NULL,
[ordinal_priority] DATETIME NULL,
[milestone] BIT DEFAULT ((0)) NOT NULL,
CONSTRAINT [PK_task] PRIMARY KEY CLUSTERED ([id] ASC)
);
At this moment we need the following columns:
id - task ID
name - task name
start - task start
end - task end
parent_id - ID of the parent task
These fields allow us to load task start and end date and its location in the task hierarchy.
You can also build the tree manually using the Tasks property like this:
C#
private void LoadTasks()
{
Task group = Tasks.AddGroup("Group 1", "G1");
group.Children.Add(NewTask("Task 1", "1", DateTime.Today, DateTime.Today.AddDays(2)));
group.Children.Add(NewTask("Task 2", "2", DateTime.Today, DateTime.Today.AddDays(2)));
group.Children.AddMilestone("Milestone 1", "3", DateTime.Today.AddDays(2));
}
VB
Private Sub LoadTasks()
Dim group As Task = Tasks.AddGroup("Group 1", "G1")
group.Children.Add(NewTask("Task 1", "1", Date.Today, Date.Today.AddDays(2)))
group.Children.Add(NewTask("Task 2", "2", Date.Today, Date.Today.AddDays(2)))
group.Children.AddMilestone("Milestone 1", "3", Date.Today.AddDays(2))
End Sub
But that would require manually loading the children for each task. We will use the built-in TasksFromEnumerable() method that will let us load the whole hierarchy from a flat table:
C#
TasksFromEnumerable(
Db.GetTasksFlat().Rows,
new TaskFieldMappings()
{
TextField = "name",
IdField = "id",
StartField = "start",
EndField = "end",
ParentField = "parent_id"
}
);
VB
TasksFromEnumerable(Db.GetTasksFlat().Rows, New TaskFieldMappings() With {
.TextField = "name",
.IdField = "id",
.StartField = "start",
.EndField = "end",
.ParentField = "parent_id"})
The helper Db.GetTasksFlat() method returns a DataTable with all the records. In this example we are using the DataTable rows as the data source but it can be any Enumerable of custom objects.
The second parameter specifies the field mappings.
The TasksFromEnumerable() method loads the task tree into the Tasks property.
Loading Links (Task Dependencies)
We will load the task links using a similar procedure.
C#
public class GanttController : Controller
{
public ActionResult Backend()
{
return new Gantt().CallBack(this);
}
class Gantt : DayPilotGantt
{
protected override void OnInit(InitArgs e)
{
StartDate = new DateTime(2014, 10, 1);
Days = 60;
LoadTasks();
LoadLinks();
UpdateWithMessage("Welcome!");
}
// ...
private void LoadLinks()
{
LinksFromEnumerable(
Db.GetLinks().Rows,
new LinkFieldMappings()
{
IdField = "id",
FromField = "from",
ToField = "to",
TypeField = "type"
});
}
}
}
VB
Public Class GanttController
Inherits Controller
Public Function Backend() As ActionResult
Return (New Gantt()).CallBack(Me)
End Function
Private Class Gantt
Inherits DayPilotGantt
Protected Overrides Sub OnInit(ByVal e As InitArgs)
StartDate = New Date(2014, 10, 1)
Days = 60
LoadTasks()
LoadLinks()
UpdateWithMessage("Welcome!")
End Sub
Private Sub LoadLinks()
LinksFromEnumerable(Db.GetLinks().Rows, New LinkFieldMappings() With {
.IdField = "id",
.FromField = "from",
.ToField = "to",
.TypeField = "type"})
End Sub
End Class
End Class
The links are stored in "link" table:
DDL:
CREATE TABLE [dbo].[link] (
[id] INT IDENTITY (1, 1) NOT NULL,
[from] INT NOT NULL,
[to] INT NOT NULL,
[type] NVARCHAR (50) NOT NULL,
PRIMARY KEY CLUSTERED ([id] ASC)
);
The fields:
id - link ID
from - ID of the source task
to - ID of the target task
type - link type ("FinishToStart", "FinishToFinish", "StartToStart", "FinishToFinish")
The links can also be loaded directly using Links property but we use the LinksFromEnumerable() method to load them directly from the data source.
C#
LinksFromEnumerable(
Db.GetLinks().Rows,
new LinkFieldMappings()
{
IdField = "id",
FromField = "from",
ToField = "to",
TypeField = "type"
});
VB
Private Sub LoadLinks()
LinksFromEnumerable(Db.GetLinks().Rows, New LinkFieldMappings() With {
.IdField = "id",
.FromField = "from",
.ToField = "to",
.TypeField = "type"})
End Sub
Gantt Chart Columns
The Gantt chart control lets us display custom data in additional columns. By default it displays a single column with the task name.
We want to display three columns:
name
id
duration
First, we need to define the columns in the MVC view
MVC View (.cshtml)
@Html.DayPilotGantt("Gantt", new DayPilotGanttConfig
{
// ...
Columns =
{
new TaskColumn { Title = "Name", Property = "text"},
new TaskColumn { Title = "Id", Property = "id"},
new TaskColumn { Title = "Duration"}
}
})
MVC View (.vbhtml)
@Html.DayPilotGantt("Gantt", New DayPilotGanttConfig With
{
' ...
.Columns = New TaskColumnCollection From
{
New TaskColumn With { .Title = "Name", .Property = "text"},
New TaskColumn With { .Title = "Id", .Property = "id"},
New TaskColumn With { .Title = "Duration", .Property="milestone"}
}
})
The Title will be used for the column header and Property will be used to load the default data for each row.
In the controller, we need to make sure that the property is specified in custom tag fields. We can load custom field as tags using TagFields property of TaskFieldMappings. We add the "id" field because we want to display it in the second column:
C#
private void LoadTasks() {
TasksFromEnumerable(
Db.GetTasksFlat().Rows,
new TaskFieldMappings()
{
TextField = "name",
IdField = "id",
StartField = "start",
EndField = "end",
ParentField = "parent_id",
TagFields = "id"
}
);
}
VB
TasksFromEnumerable(Db.GetTasksFlat().Rows, New TaskFieldMappings() With {
.TextField = "name",
.IdField = "id",
.StartField = "start",
.EndField = "end",
.ParentField = "parent_id",
.TagFields = "id"})
You can also build the column content manually. We will use this option to display the task duration in the third column.
Use OnBeforeTaskRender event:
C#
protected override void OnBeforeTaskRender(BeforeTaskRenderArgs e)
{
e.Row.Columns[2].Html = (e.End - e.Start).ToString();
}
VB
Protected Overrides Sub OnBeforeTaskRender(ByVal e As BeforeTaskRenderArgs)
e.Row.Columns(2).Html = (e.End - e.Start).ToString()
End Sub
Drag and Drop: Task Moving
Drag and drop task moving is enabled by default. The only thing we need to do is add a server-side event handler that will save the change in the database:
C#
protected override void OnTaskMove(TaskMoveArgs e)
{
Db.UpdateTask(e.Id, e.NewStart, e.NewEnd, e.Text);
LoadTasksAndLinks();
Update();
}
VB
Protected Overrides Sub OnTaskMove(ByVal e As TaskMoveArgs)
Db.UpdateTask(e.Id, e.NewStart, e.NewEnd, e.Text)
LoadTasksAndLinks()
Update()
End Sub
Drag and Drop: Task Resizing
The same applies to task resizing. Just add an event handler that saves the updated task start and end.
C#
protected override void OnTaskResize(TaskResizeArgs e)
{
Db.UpdateTask(e.Id, e.NewStart, e.NewEnd, e.Text);
LoadTasksAndLinks();
Update();
}
VB
Protected Overrides Sub OnTaskResize(ByVal e As TaskResizeArgs)
Db.UpdateTask(e.Id, e.NewStart, e.NewEnd, e.Text)
LoadTasksAndLinks()
Update()
End Sub
Drag and Drop: Link Creating
The Gantt chart control supports drag and drop link creating. It is enabled by default.
The OnLinkCreate event handler allows us to save the new link in the database.
C#
protected override void OnLinkCreate(LinkCreateArgs e)
{
Db.InsertLink(e.FromId, e.ToId, e.Type.ToString());
LoadTasksAndLinks();
Update();
}
VB
Protected Overrides Sub OnLinkCreate(ByVal e As LinkCreateArgs)
Db.InsertLink(e.FromId, e.ToId, e.Type.ToString())
LoadTasksAndLinks()
Update()
End Sub
Drag and Drop: Row Moving
The Gantt chart supports drag and drop changes to the task hierarchy.
It's not enabled by default and we need to update it in the MVC view config using RowMoveHandling property:
Index.cshtml
@Html.DayPilotGantt("Gantt", new DayPilotGanttConfig
{
BackendUrl = Url.Action("Backend", "Gantt"),
// ...
RowMoveHandling = RowMoveHandlingType.Notify
})
Index.vbhtml
@Html.DayPilotGantt("Gantt", New DayPilotGanttConfig With
{
.BackendUrl = Url.Action("Backend", "Gantt"),
' ...
.RowMoveHandling = RowMoveHandlingType.Notify
})
The Notify handling type means the row position will be updated on the client side first. The server-side event handler will be fired after the change. No refresh is required.
You may only need to force the update if the hierarchy update affect some of the row or task properties that need to be updated from the server side.
The server-side OnRowMove handler is a bit more complicated because we need to update the task order correctly:
C#
protected override void OnRowMove(RowMoveArgs e)
{
const int max = Int32.MaxValue; // make sure it's greater than any other ordinal value
Task sourceParent = Tasks.FindParent(e.Source);
Task targetParent = Tasks.FindParent(e.Target);
DataRow targetRow = Db.GetTask(e.Target.Id);
string targetParentId = targetParent != null ? targetParent.Id : null;
string sourceParentId = sourceParent != null ? sourceParent.Id : null;
int targetOrdinal = (int)targetRow["ordinal"];
switch (e.Position)
{
case RowMovePosition.Before:
Db.UpdateTaskParent(e.Source.Id, targetParentId, targetOrdinal);
break;
case RowMovePosition.After:
Db.UpdateTaskParent(e.Source.Id, targetParentId, targetOrdinal + 1);
break;
case RowMovePosition.Child:
Db.UpdateTaskParent(e.Source.Id, e.Target.Id, max);
targetParentId = e.Target.Id;
break;
case RowMovePosition.Forbidden:
break;
default:
throw new ArgumentOutOfRangeException();
}
Db.CompactOrdinals(sourceParentId);
if (sourceParentId != targetParentId)
{
Db.CompactOrdinals(targetParentId);
}
}
VB
Protected Overrides Sub OnRowMove(ByVal e As RowMoveArgs)
Const max As Integer = Int32.MaxValue ' make sure it's greater than any other ordinal value
Dim sourceParent As Task = Tasks.FindParent(e.Source)
Dim targetParent As Task = Tasks.FindParent(e.Target)
'DataRow sourceRow = DbGetTask(e.Source.Id);
Dim targetRow As DataRow = Db.GetTask(e.Target.Id)
Dim targetParentId As String = If(targetParent IsNot Nothing, targetParent.Id, Nothing)
Dim sourceParentId As String = If(sourceParent IsNot Nothing, sourceParent.Id, Nothing)
Dim targetOrdinal As Integer = DirectCast(targetRow("ordinal"), Integer)
Select Case e.Position
Case RowMovePosition.Before
Db.UpdateTaskParent(e.Source.Id, targetParentId, targetOrdinal)
Case RowMovePosition.After
Db.UpdateTaskParent(e.Source.Id, targetParentId, targetOrdinal + 1)
Case RowMovePosition.Child
Db.UpdateTaskParent(e.Source.Id, e.Target.Id, max)
targetParentId = e.Target.Id
Case RowMovePosition.Forbidden
Case Else
Throw New ArgumentOutOfRangeException()
End Select
Db.CompactOrdinals(sourceParentId)
If sourceParentId <> targetParentId Then
Db.CompactOrdinals(targetParentId)
End If
End Sub
We use the ordinal and ordinal_priority fields to store the task order. The OnRowMove handler updates the fields and compacts the numbers in the ordinal field after the changes are made.
Adding a New Task Inline
The Gantt chart control includes built-in support for adding a new task.
You need to enable the new task row in the MVC View using RowCreateHandling property:
Index.cshtml
@Html.DayPilotGantt("Gantt", new DayPilotGanttConfig
{
BackendUrl = Url.Action("Backend", "Gantt"),
// ...
RowCreateHandling = RowCreateHandlingType.CallBack
})
Index.vbhtml
@Html.DayPilotGantt("Gantt", New DayPilotGanttConfig With
{
.BackendUrl = Url.Action("Backend", "Gantt"),
' ...
.RowCreateHandling = RowCreateHandlingType.CallBack
})
When the user finishes entering the new task name the Gantt chart will fire OnRowCreate event on the server side:
C#
protected override void OnRowCreate(RowCreateArgs e)
{
DateTime start = StartDate;
DateTime end = start.AddDays(1);
string text = e.Text;
int maxOrdinal = Db.GetMaxOrdinal(null);
Db.InsertTask(start, end, text, null, maxOrdinal + 1);
LoadTasksAndLinks();
Update();
}
VB
Protected Overrides Sub OnRowCreate(ByVal e As RowCreateArgs)
Dim start As Date = StartDate
Dim [end] As Date = start.AddDays(1)
Dim text As String = e.Text
Dim maxOrdinal As Integer = Db.GetMaxOrdinal(Nothing)
Db.InsertTask(start, [end], text, Nothing, maxOrdinal + 1)
LoadTasksAndLinks()
Update()
End Sub
The handler will create a new task at the top level and refresh the Gantt chart using Update().