You are on page 1of 13

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

NET)
May 18, 2009 [updated October 15, 2012]

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

Online Demo

Tutorial Demo

Download

Tutorial Source Code (TutorialHotel.20121015.zip, 1.19 MB) - ASP.NET Project (C# and VB.NET) Includes DayPilot Pro for ASP.NET WebForms 7.1 SP2 Trial (download the latest trial version).

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

Requirements

.NET Framework 4.0 or higher Visual Studio 2010 or higher (optional)

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 usingSQLite 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. TheScheduler 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); } 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 SubProtected Sub ButtonCancel_Click(ByVal sender As Object, ByVal e As EventArgs) Handles ButtonCancel.Click Modal.Close(Me) ' do not send anything End Sub // do not send anything

} {

More Tutorials
All tutorials include a sample Visual Studio project with C# and VB.NET source code.

You might also like