DayPilot for ASP.NET - AJAX Calendar/Scheduling Controls
Try the online demo: AJAX-style event creating, moving, resizing, and deleting • Context menu • Day view • Work week view • Week view • Month view • Horizontal/vertical resources view • PostBack/AJAX/JavaScript event handling • Binding to XmlDataSource, SqlDataSource, DataTable, ArrayList • Custom event formatting • UpdatePanel compatibility

Scheduler Tutorial: Hotel Room Booking (ASP.NET, C#, VB.NET)

May 18, 2009 [updated July 17, 2009]

This tutorial shows how to use DayPilot Scheduler to build a hotel room booking application.

Online Demo

Download

Features

  • Colors indicating the reservation phase
  • Rules for reservation updates (overlap forbidden, past reservations)
  • Filters for visible rooms (based on drop-down)
  • Modal dialog for event creating and editing
  • Flash notification about user action result

Database setup

In the tutorial, we are using an embedded SQLite engine (with SQLite ADO.NET Provider) so you don't have to set anything up in order to run the demo.

The database contains two tables:

  • event
  • resource

The [event] table stores the events (reservations).

CREATE TABLE [event] (
[id] varchar(30)  UNIQUE NOT NULL,
[name] varchar (50)  NULL,
[eventstart] datetime  NULL,
[eventend] datetime  NULL,
[resource_id] varchar(50)  NULL,
[status] INTEGER DEFAULT '0' NOT NULL
);

The [resource] table stores the room details.

CREATE TABLE [resource] (
[id] VARCHAR(50)  UNIQUE NOT NULL,
[name] VARCHAR(200)  NULL,
[beds] INTEGER  NULL,
[bath] VARCHAR(50)  NULL
);

The database file (daypilot.sqlite) can be found in App_Data directory. The database structure can be examined using SQLite Administrator.

Marking the past days

The Scheduler offers several ways to show the current date/time (e.g. Separators). We will use BeforeCellRender to show past days in different color:

C#

    protected void DayPilotScheduler1_BeforeCellRender(object sender, DayPilot.Web.Ui.Events.BeforeCellRenderEventArgs e)
    {
        if (e.Start < DateTime.Today)
        {
            if (e.IsBusiness)
            {
                e.BackgroundColor = "#D5D5C0";
            }
            else
            {
                e.BackgroundColor = "#D5CFB3";
            }
        }
    }

VB.NET

    Protected Sub DayPilotScheduler1_BeforeCellRender(ByVal sender As Object, ByVal e As DayPilot.Web.Ui.Events.BeforeCellRenderEventArgs) Handles DayPilotScheduler1.BeforeCellRender
        If e.Start < DateTime.Today Then
            If e.IsBusiness Then
                e.BackgroundColor = "#D5D5C0"
            Else
                e.BackgroundColor = "#D5CFB3"
            End If
        End If
    End Sub

Event colors indicating the reservation phase

In our application, we will use four reservation phases:

  • New reservation (unconfirmed)
  • Confirmed reservation
  • Arrived (checked in)
  • Checked out

Each phase will use a specific DurationBar color.

New reservation (Orange)

Confirmed reservation (Green)

Arrived (Blue)

Checked out (Gray)

Problem (Red)

The Red color is used for reservation with a problem:

  • New reservations not confirmed 2 days before the start day
  • Confirmed reservations not checked in before 6pm on the start day
  • Stays not checked out before 10 am on the end day

The color is assigned using BeforeEventRender event handler.

  • Note that the "status" database field is used to determine the phase
  • The status is loaded into the Tag collection using DataTagFields="status" declaration
  • In the event handlers, it's available as e.Tag["status"] in the event handler

C#

    protected void DayPilotScheduler1_BeforeEventRender(object sender, DayPilot.Web.Ui.Events.BeforeEventRenderEventArgs e)
    {
        e.InnerHTML = String.Format("{0} ({1:d} - {2:d})", e.Text, e.Start, e.End);
        int status = Convert.ToInt32(e.Tag["status"]);
        switch (status)
        {
            case 0: // new
                if (e.Start < DateTime.Today.AddDays(2)) // must be confirmed two day in advance
                {
                    e.DurationBarColor = "red";
                    e.ToolTip = "Expired (not confirmed in time)";
                }
                else
                {
                    e.DurationBarColor = "orange";
                    e.ToolTip = "New";
                }
                break;
            case 1:  // confirmed
                if (e.Start < DateTime.Today || (e.Start == DateTime.Today && DateTime.Now.TimeOfDay.Hours > 18))  // must arrive before 6 pm
                {
                    e.DurationBarColor = "red";
                    e.ToolTip = "Late arrival";
                }
                else
                {
                    e.DurationBarColor = "green";
                    e.ToolTip = "Confirmed";
                }
                break;
            case 2: // arrived
                if (e.End < DateTime.Today || (e.End == DateTime.Today && DateTime.Now.TimeOfDay.Hours > 11))  // must checkout before 10 am
                {
                    e.DurationBarColor = "red";
                    e.ToolTip = "Late checkout";
                }
                else
                {
                    e.DurationBarColor = "blue";
                    e.ToolTip = "Arrived";
                }
                break;
            case 3: // checked out
                e.DurationBarColor = "gray";
                e.ToolTip = "Checked out";
                break;
            default:
                throw new ArgumentException("Unexpected status.");
        }

VB.NET

    Protected Sub DayPilotScheduler1_BeforeEventRender(ByVal sender As Object, ByVal e As DayPilot.Web.Ui.Events.BeforeEventRenderEventArgs) Handles DayPilotScheduler1.BeforeEventRender
        e.InnerHTML = [String].Format("{0} ({1:d} - {2:d})", e.Text, e.Start, e.[End])
        Dim status As Integer = Convert.ToInt32(e.Tag("status"))
        Select Case status
            Case 0
                ' new
                If e.Start < DateTime.Today.AddDays(2) Then
                    ' must be confirmed two day in advance
                    e.DurationBarColor = "red"
                    e.ToolTip = "Expired (not confirmed in time)"
                Else
                    e.DurationBarColor = "orange"
                    e.ToolTip = "New"
                End If
                Exit Select
            Case 1
                ' confirmed
                If e.Start < DateTime.Today OrElse (e.Start = DateTime.Today AndAlso DateTime.Now.TimeOfDay.Hours > 18) Then
                    ' must arrive before 6 pm
                    e.DurationBarColor = "red"
                    e.ToolTip = "Late arrival"
                Else
                    e.DurationBarColor = "green"
                    e.ToolTip = "Confirmed"
                End If
                Exit Select
            Case 2
                ' arrived
                If e.[End] < DateTime.Today OrElse (e.[End] = DateTime.Today AndAlso DateTime.Now.TimeOfDay.Hours > 11) Then
                    ' must checkout before 10 am
                    e.DurationBarColor = "red"
                    e.ToolTip = "Late checkout"
                Else
                    e.DurationBarColor = "blue"
                    e.ToolTip = "Arrived"
                End If
                Exit Select
            Case 3
                ' checked out
                e.DurationBarColor = "gray"
                e.ToolTip = "Checked out"
                Exit Select
            Case Else
                Throw New ArgumentException("Unexpected status.")
        End Select
        e.InnerHTML = e.InnerHTML + [String].Format("<br /><span style='color:gray'>{0}</span>", e.ToolTip)
    End Sub

Reservation change rules

The booking logic imposes some restrictions. We will implement the following rules:

  • No room can be booked for two guests at the same time (no overlap allowed).
  • The reservations that already checked in can't be moved to another room.
  • The start of reservations that already checked in can't be changed.
  • The end of reservations that already checked out can't be changed.

The Scheduler handles the drag&drop user actions (move and resize) using EventMove and EventResize event handlers.

EventMove Event Handler

C#

    protected void DayPilotScheduler1_EventMove(object sender, DayPilot.Web.Ui.Events.EventMoveEventArgs e)
    {
        string id = e.Value;
        DateTime start = e.NewStart;
        DateTime end = e.NewEnd;
        string resource = e.NewResource;
        string message = null;
        if (!dbIsFree(id, start, end, resource))
        {
            message = "The reservation cannot overlap with an existing reservation).";
        }
        else if (e.OldEnd <= DateTime.Today)
        {
            message = "This reservation cannot be changed anymore.";
        }
        else if (e.OldStart < DateTime.Today)
        {
            if (e.OldResource != e.NewResource)
            {
                message = "The room cannot be changed anymore.";
            }
            else
            {
                message = "The reservation start cannot be changed anymore.";
            }
        }
        else if (e.NewStart < DateTime.Today)
        {
            message = "The reservation cannot be moved to the past.";
        }
        else
        {
            dbUpdateEvent(id, start, end, resource);
            //message = "Reservation moved.";
        }
        DayPilotScheduler1.DataSource = dbGetEvents(DayPilotScheduler1.StartDate, DayPilotScheduler1.Days);
        DayPilotScheduler1.DataBind();
        DayPilotScheduler1.Update(message);
    }

VB.NET

    Protected Sub DayPilotScheduler1_EventMove(ByVal sender As Object, ByVal e As DayPilot.Web.Ui.Events.EventMoveEventArgs) Handles DayPilotScheduler1.EventMove
        Dim id As String = e.Value
        Dim start As DateTime = e.NewStart
        Dim [end] As DateTime = e.NewEnd
        Dim resource As String = e.NewResource
        Dim message As String = Nothing

        If Not dbIsFree(id, start, [end], resource) Then
            message = "The reservation cannot overlap with an existing reservation."
        ElseIf e.OldEnd <= DateTime.Today Then
            message = "This reservation cannot be changed anymore."
        ElseIf e.OldStart < DateTime.Today Then
            If e.OldResource <> e.NewResource Then
                message = "The room cannot be changed anymore."
            Else
                message = "The reservation start cannot be changed anymore."
            End If
        ElseIf e.NewStart < DateTime.Today Then
            message = "The reservation cannot be moved to the past."
        Else
            'message = "Reservation moved.";
            dbUpdateEvent(id, start, [end], resource)
        End If

        DayPilotScheduler1.DataSource = dbGetEvents(DayPilotScheduler1.StartDate, DayPilotScheduler1.Days)
        DayPilotScheduler1.DataBind()
        DayPilotScheduler1.Update(message)
    End Sub

EventResize Event Handler

C#

    protected void DayPilotScheduler1_EventResize(object sender, DayPilot.Web.Ui.Events.EventResizeEventArgs e)
    {
        string id = e.Value;
        DateTime start = e.NewStart;
        DateTime end = e.NewEnd;
        string resource = e.Resource;
        string message = null;
        if (!dbIsFree(id, start, end, resource))
        {
            message = "The reservation cannot overlap with an existing reservation).";
        }
        else if (e.OldEnd <= DateTime.Today)
        {
            message = "This reservation cannot be changed anymore.";
        }
        else if (e.OldStart != e.NewStart)
        {
            if (e.OldStart < DateTime.Today)
            {
               message = "The reservation start cannot be changed anymore.";
            }
            else if (e.NewStart < DateTime.Today)
            {
                message = "The reservation cannot be moved to the past.";
            }
        }
        else
        {
            dbUpdateEvent(id, start, end, resource);
            //message = "Reservation updated.";
        }
        DayPilotScheduler1.DataSource = dbGetEvents(DayPilotScheduler1.StartDate, DayPilotScheduler1.Days);
        DayPilotScheduler1.DataBind();
        DayPilotScheduler1.Update(message);
    }

VB.NET

    Protected Sub DayPilotScheduler1_EventResize(ByVal sender As Object, ByVal e As DayPilot.Web.Ui.Events.EventResizeEventArgs) Handles DayPilotScheduler1.EventResize
        Dim id As String = e.Value
        Dim start As DateTime = e.NewStart
        Dim [end] As DateTime = e.NewEnd
        Dim resource As String = e.Resource
        Dim message As String = Nothing

        If Not dbIsFree(id, start, [end], resource) Then
            message = "The reservation cannot overlap with an existing reservation."
        ElseIf e.OldEnd <= DateTime.Today Then
            message = "This reservation cannot be changed anymore."
        ElseIf e.OldStart <> e.NewStart Then
            If e.OldStart < DateTime.Today Then
                message = "The reservation start cannot be changed anymore."
            ElseIf e.NewStart < DateTime.Today Then
                message = "The reservation cannot be moved to the past."
            End If
        Else
            'message = "Reservation updated.";
            dbUpdateEvent(id, start, [end], resource)
        End If

        DayPilotScheduler1.DataSource = dbGetEvents(DayPilotScheduler1.StartDate, DayPilotScheduler1.Days)
        DayPilotScheduler1.DataBind()
        DayPilotScheduler1.Update(message)
    End Sub

Note that the database is updated only when the change meets the rules.

User notifications

Because the requested change is not allowed in some cases, we need to notify the users about the reason. The Scheduler can send a custom message to the client using Update() method. Just add a parameter to the Update() call in the event handlers (EventMove and EventResize):

DayPilotScheduler1.Update("The reservation cannot overlap with an existing reservation.");

On the client side, the following steps are necessary:

1. Put a message placeholder in the HTML code. The message box should be hidden using "display:none" style:

    <div>
        <span id="message" style="padding:2px; display: none;" class="message_warn"></span>
    </div>

2. Include the message.js JavaScript library:

 <script type="text/javascript" src="js/message.js"></script>

3. Handle AfterRender event on the client side. Check if any message was sent from the server and show it:

    <DayPilot:DayPilotScheduler 
        ID="DayPilotScheduler1"
        runat="server"
        ...       
        AfterRenderJavaScript="afterRender(data);"
        >
    </DayPilot:DayPilotScheduler>
 function afterRender(data) {
        new DayPilot2.Message("message").show(data); 
};

Room filtering

We add a DropDown control just above the Scheduler control to enable room filtering.

        <asp:DropDownList ID="DropDownListFilter" runat="server" onchange="filter('room', this.value)">
        <asp:ListItem Text="All" Value="0"></asp:ListItem>
        <asp:ListItem Text="Single" Value="1"></asp:ListItem>
        <asp:ListItem Text="Double" Value="2"></asp:ListItem>
        <asp:ListItem Text="Tripple" Value="3"></asp:ListItem>
        <asp:ListItem Text="Family" Value="4"></asp:ListItem>
        </asp:DropDownList>

The DropDown value changes are handled using a custom JavaScript on the client side (onchange attribute). This JavaScript updates the scheduler using a fast callback refresh:

 function filter(property, value) {
     if (!dps.clientState.filter) {
         dps.clientState.filter = {};
     }
     if (dps.clientState.filter[property] != value) { // only refresh when the value has changed
         dps.clientState.filter[property] = value;
         dps.commandCallBack('filter');
     }
 }

DayPilot Scheduler has a special client-side property which is sent to the server with every callback (clientState).

  • The selected value is stored in clientState after every DropDown selection change.
  • We are creating a new filter object but it can be as simple as storing the filter value in the clientState directly. However, then you wouldn't be able to store more complicated filters (state of several controls) in clientState:
 function filter(property, value) {
         dps.clientState = value;
         dps.commandCallBack('filter');
 }

On the server side, we need to handle Command event and refresh the room and reservation lists:

C#

    protected void DayPilotScheduler1_Command(object sender, DayPilot.Web.Ui.Events.CommandEventArgs e)
    {
        switch (e.Command)
        {
            case "filter":
                loadResources();
                DayPilotScheduler1.DataSource = dbGetEvents(DayPilotScheduler1.StartDate, DayPilotScheduler1.Days);
                DayPilotScheduler1.DataBind();
                DayPilotScheduler1.Update(CallBackUpdateType.Full);
                break;
        }
    }

VB.NET

    Protected Sub DayPilotScheduler1_Command(ByVal sender As Object, ByVal e As DayPilot.Web.Ui.Events.CommandEventArgs) Handles DayPilotScheduler1.Command
        Select Case e.Command
            Case "filter"
                loadResources()
                DayPilotScheduler1.DataSource = dbGetEvents(DayPilotScheduler1.StartDate, DayPilotScheduler1.Days)
                DayPilotScheduler1.DataBind()
                DayPilotScheduler1.Update(CallBackUpdateType.Full)
                Exit Select
        End Select
    End Sub

Modal dialog for event creating and editing

There are several ways to show the edit dialog:

  • Popup window [window.open() in JavaScript]
  • Redirect to another URL [Response.Redirect() or document.location.href]
  • Modal dialog [window.showModalDialog(), Internet Explorer only]
  • ModalPopupExtender [Ajax Control Toolkit]

The most interesting option is the ModalPopupExtender but it has two disadvantages:

  • You need to use ASP.NET AJAX Extensions
  • The markup and the code are embedded in the same page (making the main page more difficult to work with)
  • In order to change the dialog content dynamically, you need to put it another UpdatePanel inside the modal dialog

This is a common scenario and you have probably used it already.

So we will offer you another option, similar to window.showModalDialog():

Features:

  • Conceptually similar to showModalDialog(), but working in all browsers
  • Shows another page in a new <iframe>
  • A JSON result can be returned from the dialog to the main page.
  • Small footprint: js/modal.js library (3kB uncompressed), App_Code/Modal.cs (1 kB)

Usage:

  1. Copy modal.js to js directory.
  2. Copy Modal.cs to App_Code directory.
  3. Include modal.js in Default.aspx: <script type="text/javascript" src="js/modal.js"></script>

The modal dialog is invoked using a short piece of JavaScript:

new DayPilot2.Modal().showUrl("Detail.aspx");

The Detail.aspx page needs to call a Modal.Close() in order to close the modal window:

C#

    protected void ButtonOK_Click(object sender, EventArgs e)
    {
        // do your job here
        Modal.Close(this);
    }

VB.NET

Protected Sub ButtonOK_Click(ByVal sender As Object, ByVal e As EventArgs) Handles ButtonOK.Click
    ' do your job here       
    Modal.Close(Me)
End Sub

In order to handle the dialog result we need to use a bit more complex JavaScript initialization:

function createEvent(start, end, resource) {
    var modal = new DayPilot2.Modal();
    modal.top = 60;
    modal.height = 250;
    modal.width = 300;
    modal.opacity = 60;
    modal.border = "1px solid black";
    modal.closed = function() {
        if(this.result == "OK") {
            dps.commandCallBack('refresh');
        }
    };
    modal.showUrl("New.aspx?start=" + start.toStringSortable() + "&end=" + end.toStringSortable() + "&r=" + resource);
}

The most important command is assigning the closed handler.

This handler will check the result sent from the modal and refresh using commandCallBack if changes were made.

The result can be sent using Modal.Close as well:

C#

    protected void ButtonOK_Click(object sender, EventArgs e)
    {
        DateTime start = Convert.ToDateTime(TextBoxStart.Text);
        DateTime end = Convert.ToDateTime(TextBoxEnd.Text);
        string name = TextBoxName.Text;
        string resource = DropDownList1.SelectedValue;

        dbInsertEvent(start, end, name, resource, 0);
        Modal.Close(this, "OK");
    }
    protected void ButtonCancel_Click(object sender, EventArgs e)
    {
        Modal.Close(this);  // do not send anything
    }

VB.NET

Protected Sub ButtonOK_Click(ByVal sender As Object, ByVal e As EventArgs) Handles ButtonOK.Click
    Dim start As DateTime = Convert.ToDateTime(TextBoxStart.Text)
    Dim [end] As DateTime = Convert.ToDateTime(TextBoxEnd.Text)
    Dim name As String = TextBoxName.Text
    Dim resource As String = DropDownList1.SelectedValue
    dbInsertEvent(start, [end], name, resource, 0)
    Modal.Close(Me, "OK")
End Sub
Protected Sub ButtonCancel_Click(ByVal sender As Object, ByVal e As EventArgs) Handles ButtonCancel.Click
    Modal.Close(Me) ' do not send anything
End Sub

DayPilot Pro is an advanced DayPilot edition. You can check a thumbnail overview of the most interesting features. There is also an online demo with all the features working (including the AJAX features). If you want to test the design-time support and API you can download a fully functional trial version. And if you like it, you can buy a full version with source code and 12 months of upgrades and support (with a 30-days money back guarantee).

DayPilot Lite is a do-it-yourself open-source edition of DayPilot. Although it misses some DayPilot Pro features, there are thousands of developers using it to build calendar, personal scheduling, and resource booking applications.