Sample Project

The sample project includes:

Requirements

  • .NET Framework 4.0 or higher

  • Visual Studio 2019 or higher (optional)

  • Microsoft SQL Server (LocalDB) 

Features

This tutorial shows how to create a courtroom schedule web application using DayPilot ASP.NET Scheduler control.

  • Queue of unscheduled cases

  • Courtrooms displayed in a tree (with buildings as parents)

  • Scheduling cases using drag & drop from the list of unscheduled cases

  • Available time slots limited to working hours (8 am - 6 pm)

  • Custom CSS theme

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 WebForms.

1. How to create a queue of unscheduled cases

courtroom schedule case queue

The application displays unscheduled cases in a separate list, next to the Scheduler component.

All cases are stored in the Task table in the SQL Server database. Cases that have no Assignment associated to them will be displayed in the queue of unscheduled tasks.

The task list is generated using a simple Repeater control (AjaxTasks.aspx). 

<asp:Repeater runat="server" id="Repeater1">
    <ItemTemplate>
        <div class="task" onclick="editTask('<%# DataBinder.Eval(Container.DataItem, "TaskId") %>')" onmousedown="return DayPilotScheduler.dragStart(this, 60*<%# DataBinder.Eval(Container.DataItem, "TaskDuration") %>, '<%# DataBinder.Eval(Container.DataItem, "TaskId") %>', this.innerHTML);">
		<span style="cursor: move">:::</span> <%# DataBinder.Eval(Container.DataItem, "TaskName") %> <span><%# DataBinder.Eval(Container.DataItem, "TaskDuration") %>m</span>
    	</div>
    </ItemTemplate>
</asp:Repeater>

The drag & drop functionality is activated by adding an onmousedown attribute to the list items:

onmousedown="return DayPilotScheduler.dragStart(this, 60*<%# DataBinder.Eval(Container.DataItem, "TaskDuration") %>, '<%# DataBinder.Eval(Container.DataItem, "TaskId") %>', this.innerHTML);"

See also: External Drag and Drop [Scheduler documentation]

The list is is loaded dynamically using a separate AJAX call (jQuery) from the AjaxTasks.aspx page:

function getTasks() {
  $.post('AjaxTasks.aspx', function (result) {
    $("#tasks").html(result);
  });
}

2. How to display a courtroom schedule

courtroom schedule locations

The courtrooms are loaded to the ASP.NET Scheduler control from the Location database table.

C#

private void LoadResources()
{
  DataTable locations = new DataManager().GetLocations();
  DayPilotScheduler1.Resources.Clear();
  foreach (DataRow location in locations.Rows)
  {
    int id = Convert.ToInt32(location["LocationId"]);

    Resource r = new Resource((string)location["LocationName"], null); // using null for resources that can't be used
    r.Expanded = true;
    DayPilotScheduler1.Resources.Add(r);

    DataTable rooms = new DataManager().GetLocations(id);
    foreach (DataRow dr in rooms.Rows)
    {
      r.Children.Add((string) dr["LocationName"], Convert.ToString(dr["LocationId"]));
    }
  }
}

VB.NET

Private Sub LoadResources()
  Dim locations As DataTable = (New DataManager()).GetLocations()
  DayPilotScheduler1.Resources.Clear()
  For Each location As DataRow In locations.Rows
    Dim id As Integer = Convert.ToInt32(location("LocationId"))

    Dim r As New Resource(CStr(location("LocationName")), Nothing) ' using null for resources that can't be used
    r.Expanded = True
    DayPilotScheduler1.Resources.Add(r)

    Dim rooms As DataTable = (New DataManager()).GetLocations(id)
    For Each dr As DataRow In rooms.Rows
      r.Children.Add(CStr(dr("LocationName")), Convert.ToString(dr("LocationId")))
    Next dr
  Next location
End Sub

Buildings are loaded first:

C#

DataTable locations = new DataManager().GetLocations();

VB.NET

Dim locations As DataTable = (New DataManager()).GetLocations()

For each building, the list of courtrooms is loaded (using the building id as the parent):

C#

DataTable rooms = new DataManager().GetLocations(id);

VB.NET

Dim rooms As DataTable = (New DataManager()).GetLocations(id)

Creating assignments for buildings (parent tree nodes) is disabled using the PreventParentUsage property:

PreventParentUsage="false"

The parent nodes are marked with prefix_cellparent CSS class. This allows to add a visual hint:

.blue_light_header_cellparent {
	background-color: #f8f8f8;
}

3. How to create assignments using drag & drop

courtroom schedule drag and drop

Unscheduled tasks can be dragged onto the scheduler. The drop event is handled using an EventMove event handler on the server side:

EventMoveHandling="JavaScript"
EventMoveJavaScript="dp.eventMoveCallBack(e, newStart, newEnd, newResource, {external: external} );"

It is handled using a custom JavaScript handler so we can detect the external drag and drop on the server side (the external parameter).

C#

protected void DayPilotScheduler1_EventMove(object sender, EventMoveEventArgs e)
{
	// check if this row is a courtroom
	if (e.NewResource == null)
	{
		DayPilotScheduler1.DataSource = new DataManager().GetAssignments(DayPilotScheduler1);
		DayPilotScheduler1.DataBind();
		DayPilotScheduler1.UpdateWithMessage("Sorry, this is not a courtroom. The case cannot be scheduled here.");
		return;
	}

	// check for conflicts with existing assignments
	int existing = new DataManager().GetExistingAssignments(e.Value, e.NewStart, e.NewEnd, e.NewResource);
	if (existing > 0)
	{
		DayPilotScheduler1.DataSource = new DataManager().GetAssignments(DayPilotScheduler1);
		DayPilotScheduler1.DataBind();
		DayPilotScheduler1.UpdateWithMessage(String.Format("The case cannot be scheduled here. It conflicts with {0} other assignments.", existing));
		return;
	}

	// check the business hours (8 - 18)
	if (e.NewStart.Hour < 8 || e.NewEnd > new DateTime(e.NewEnd.Year, e.NewEnd.Month, e.NewEnd.Day, 18, 0, 0))
	{
		DayPilotScheduler1.DataSource = new DataManager().GetAssignments(DayPilotScheduler1);
		DayPilotScheduler1.DataBind();
		DayPilotScheduler1.UpdateWithMessage("Sorry, it can't be scheduled outside of the business hours.");
		return;
	}

	// scheduling a new case
	if ((bool)e.Data["external"])
	{
		new DataManager().CreateAssignment(e.NewStart, e.NewEnd, Convert.ToInt32(e.NewResource), Convert.ToInt32(e.Value));
		DayPilotScheduler1.UpdateWithMessage("The assignment has been created.");
	}
	// moving an existing assignment
	else
	{
		new DataManager().MoveAssignment(Convert.ToInt32(e.Value), e.NewStart, e.NewEnd, e.NewResource);
		DayPilotScheduler1.UpdateWithMessage("The assignment has been updated.");
	}

	DayPilotScheduler1.DataSource = new DataManager().GetAssignments(DayPilotScheduler1);
	DayPilotScheduler1.DataBind();

}

VB.NET

Protected Sub DayPilotScheduler1_EventMove(ByVal sender As Object, ByVal e As EventMoveEventArgs)
  ' check if this row is a courtroom
  If e.NewResource Is Nothing Then
    DayPilotScheduler1.DataSource = (New DataManager()).GetAssignments(DayPilotScheduler1)
    DayPilotScheduler1.DataBind()
    DayPilotScheduler1.UpdateWithMessage("Sorry, this is not a courtroom. The case cannot be scheduled here.")
    Return
  End If

  ' check for conflicts with existing assignments
  Dim existing As Integer = (New DataManager()).GetExistingAssignments(e.Value, e.NewStart, e.NewEnd, e.NewResource)
  If existing > 0 Then
    DayPilotScheduler1.DataSource = (New DataManager()).GetAssignments(DayPilotScheduler1)
    DayPilotScheduler1.DataBind()
    DayPilotScheduler1.UpdateWithMessage(String.Format("The case cannot be scheduled here. It conflicts with {0} other assignments.", existing))
    Return
  End If

  ' check the business hours (8 - 18)
  If e.NewStart.Hour < 8 OrElse e.NewEnd > New Date(e.NewEnd.Year, e.NewEnd.Month, e.NewEnd.Day, 18, 0, 0) Then
    DayPilotScheduler1.DataSource = (New DataManager()).GetAssignments(DayPilotScheduler1)
    DayPilotScheduler1.DataBind()
    DayPilotScheduler1.UpdateWithMessage("Sorry, it can't be scheduled outside of the business hours.")
    Return
  End If

  ' scheduling a new case
  If CBool(e.Data("external")) Then
    CType(New DataManager(), DataManager).CreateAssignment(e.NewStart, e.NewEnd, Convert.ToInt32(e.NewResource), Convert.ToInt32(e.Value))
    DayPilotScheduler1.UpdateWithMessage("The assignment has been created.")
  ' moving an existing assignment
  Else
    CType(New DataManager(), DataManager).MoveAssignment(Convert.ToInt32(e.Value), e.NewStart, e.NewEnd, e.NewResource)
    DayPilotScheduler1.UpdateWithMessage("The assignment has been updated.")
  End If

  DayPilotScheduler1.DataSource = (New DataManager()).GetAssignments(DayPilotScheduler1)
  DayPilotScheduler1.DataBind()

End Sub

The EventMove event handler performs a few checks before actually saving the changes:

  1. Check if the resource (row) is a room, and not a building.

  2. Check if there is no other case scheduled for the same room and time.

  3. Check if the case is scheduled within the working hours.

In the case of external drag & drop (from the list of unscheduled tasks) it creates a new record in the Assignment table.

Otherwise, it updates the existing Assignment.

4. How to define working hours

courtroom schedule working hours

By default, the ASP.NET Scheduler displays a full 24-hour range for each day.

We can limit it to the actual working hours using the ShowNonBusiness property:

BusinessBeginsHour="8"
BusinessEndsHour="18"
ShowNonBusiness="false"

See also: Hiding Non-Business Hours

5. How to style the ASP.NET Scheduler using CSS rheme

scheduler css theme admsiu

The CSS theme was created using the online CSS Theme Designer. It is saved at the following URL:

You can apply a theme using the Theme property:

Theme="blue_light_header"

6. SQL Server database schema

This is the schema (DDL) of the attached SQL Server database (daypilot.mdf):

Assignment table:

CREATE TABLE [dbo].[Assignment] (
    [AssignmentId]    BIGINT         IDENTITY (1, 1) NOT NULL,
    [LocationId]      BIGINT         NOT NULL,
    [AssignmentNote]  VARCHAR (2000) NULL,
    [AssignmentStart] DATETIME       NOT NULL,
    [AssignmentEnd]   DATETIME       NOT NULL,
    [TaskId]          BIGINT         NULL,
    CONSTRAINT [PK_Assignment] PRIMARY KEY CLUSTERED ([AssignmentId] ASC)
);

Location table:

CREATE TABLE [dbo].[Location] (
    [LocationId]   BIGINT        IDENTITY (1, 1) NOT NULL,
    [LocationName] VARCHAR (200) NOT NULL,
    [ParentId]     BIGINT        NULL,
    CONSTRAINT [PK_Location] PRIMARY KEY CLUSTERED ([LocationId] ASC)
);

Task table:

CREATE TABLE [dbo].[Task] (
    [TaskId]       BIGINT        IDENTITY (1, 1) NOT NULL,
    [TaskDuration] INT           NULL,
    [TaskName]     VARCHAR (200) NULL,
    CONSTRAINT [PK_Task] PRIMARY KEY CLUSTERED ([TaskId] ASC)
);