Hotel Room Booking Calendar in ASP.NET MVC5 (Tutorial)

Hotel Room Booking Calendar in ASP.NET MVC5 (Tutorial)

Intro

In this tutorial you’ll learn how to create a hotel room booking calendar on ASP.NET MVC 5. The created booking app will allow simple and fast room booking in a hotel. Each booking will contain info about customer, booking (determines the display color of booking) and payment status, and the associated room. Besides, each room will have its own type and сleanness status. Also, there will be select box that will filter rooms by type.

hotel room booking calendar

Technologies applied: .NET 4.5, ASP.NET MVC 5, DHTMLX Scheduler.NET, Entity Framework 6 IDE used in this tutorial: Visual Studio 2015

Architecture Overview

Our solution called “HotelRoomBooking” will contain the following assemblies:

  • HotelRoomBooking.Web - MVC 5 project
  • HotelRoomBooking.Model - class library which declares data entities and common interfaces
  • HotelRoomBooking.DataAccess.EF - class library, will provide repository pattern implementation using Entity Framework

hotel room create in asp.net

Implementation

Model

We will start implementing our solution from the infrastructure library. Open Visual Studio, create solution named “HotelRoomBooking” with class library “HotelRoomBooking.Infrastructure” Firstly, declare an interface for repository classes, which we'll use to isolate actual implementation of the data access from the rest of application. This project will contain only one item: IRepository interface, let’s create it:

public interface IRepository<TEntity>
    {
        void Create(TEntity entity);
        TEntity Read(int id);
        void Update(TEntity entity);
        void Delete(int id);
 
        IEnumerable<TEntity> ReadWhere(Func<TEntity, bool> predicate);
        IEnumerable<TEntity> ReadAll();
    }

Next, we'll declare classes for our application domain - bookings, rooms, statuses of bookings, statuses of rooms and types of rooms.

Primary entities like Booking and Room will have scheduler mapping data annotations for its properties:

  • [DHXJson(Alias = “some_alias”)] - overloads name of JSON object property, generated by Scheduler.NET, sent to the client
  • [DHXJson(Ignore = true)] - denying this property sending to the client

Scheduler.NET event (booking in our case) item contains mandatory property “text”, so we should create C# class with “text” lowercase property or create it with normal naming and map it. We’ll use the latter way.

Secondary entities, like types and statuses, will be implemented by classes (instead of enums) for better extensibility and will be initialized with db creation. Let’s create them:

public class BookingStatus
    {
        public int Id { get; set; }
        public string Title { get; set; }
    }
 
public class RoomStatus
    {
        public int Id { get; set; }
        public string Title { get; set; }
    }
 
public class RoomType
    {
        public int Id { get; set; }
        public string Title { get; set; }
    }

Install Scheduler.NET via NuGet package manager for data annotations:

PM> Install-Package DHTMLX.Scheduler.NET -ProjectName HotelRoomBooking.Entities

The Scripts folder is worthless in this class library, so it can be deleted. Let’s create primary classes:

public class Room
    {
        [DHXJson(Alias = "key")]
        public int Id { get; set; }
 
        [DHXJson(Alias = "room_number")]
        public int Number { get; set; }
 
        [DHXJson(Alias = "label")]
        public string Label { get; set; }
 
        [DHXJson(Alias = "type")]
        public int? RoomTypeId { get; set; }
 
        [DHXJson(Ignore = true)]
        public RoomType RoomType { get; set; }
 
        [DHXJson(Alias = "status")]
        public int? RoomStatusId { get; set; }
 
        [DHXJson(Ignore = true)]
        public RoomStatus RoomStatus { get; set; }
    }
 
 
public class Booking
    {
        [DHXJson(Alias = "id")]
        public int Id { get; set; }
 
        [DHXJson(Alias = "text")]
        public string Name { get; set; }
 
        [DHXJson(Alias = "is_paid")]
        public bool IsPaid { get; set; }
 
        [DHXJson(Alias = "start_date")]
        public DateTime StartDate { get; set; }
 
        [DHXJson(Alias = "end_date")]
        public DateTime EndDate { get; set; }
 
        [DHXJson(Alias = "room_number")]
        public int? RoomId { get; set; }
 
        [DHXJson(Ignore = true)]
        public virtual Room Room { get; set; }
 
        [DHXJson(Alias = "status")]
        public int? BookingStatusId { get; set; }
 
        [DHXJson(Ignore = true)]
        public BookingStatus BookingStatus { get; set; }
    }

Data access

Data Access layer will be implemented with Entity Framework 6. Let’s create class library “HotelRoomBooking.DataAccess.EF” and install the package via NuGet:

Install-Package EntityFramework -ProjectName HotelRoomBooking.DataAccess.EF

Then build HotelRoomBooking.Model project and add a reference to it from HotelRoomBooking.DataAccess.EF

Create DB context “RoomBookingContext” class:

public class RoomBookingContext : DbContext
    {
        public DbSet<Room> Rooms { get; set; }
        public DbSet<RoomStatus> RoomStatuses { get; set; }
        public DbSet<RoomType> RoomTypes { get; set; }
 
        public DbSet<Booking> Bookings { get; set; }
        public DbSet<BookingStatus> BookingStatuses { get; set; } 
    }

Afterwards, create custom “DbInitializer” class for Entity Framework, which will initialize default collections of room and booking statuses, and room type. Also, the initializer will contain some demo rooms and bookings:

public class DbInitializer : DropCreateDatabaseIfModelChanges<RoomBookingContext>
    {
        protected override void Seed(RoomBookingContext context)
        {
            var roomTypes = new List<RoomType>
            {
                new RoomType {Id = 0, Title = "1 bed"},
                new RoomType {Id = 1, Title = "2 beds"},
                new RoomType {Id = 2, Title = "3 beds"},
                new RoomType {Id = 3, Title = "4 beds"}
            };
 
            foreach (var type in roomTypes)
            {
                context.RoomTypes.Add(type);
            }
 
            var roomStatuses = new List<RoomStatus>
            {
                new RoomStatus { Id = 0, Title = "Ready" },
                new RoomStatus { Id = 1, Title = "Dirty" },
                new RoomStatus { Id = 2, Title = "Clean up" }
            };
 
            foreach (var status in roomStatuses)
            {
                context.RoomStatuses.Add(status);
            }
 
            var rooms = new List<Room>
            {
                new Room {Number = 101, RoomStatus = roomStatuses[0], RoomType = roomTypes[0]},
                new Room {Number = 102, RoomStatus = roomStatuses[1], RoomType = roomTypes[3]},
                new Room {Number = 103, RoomStatus = roomStatuses[2], RoomType = roomTypes[2]},
                new Room {Number = 104, RoomStatus = roomStatuses[1], RoomType = roomTypes[3]},
                new Room {Number = 105, RoomStatus = roomStatuses[0], RoomType = roomTypes[1]},
                new Room {Number = 201, RoomStatus = roomStatuses[2], RoomType = roomTypes[0]},
                new Room {Number = 202, RoomStatus = roomStatuses[1], RoomType = roomTypes[0]},
                new Room {Number = 203, RoomStatus = roomStatuses[1], RoomType = roomTypes[1]},
                new Room {Number = 204, RoomStatus = roomStatuses[2], RoomType = roomTypes[2]},
                new Room {Number = 301, RoomStatus = roomStatuses[0], RoomType = roomTypes[1]},
                new Room {Number = 302 , RoomStatus = roomStatuses[0], RoomType = roomTypes[0]}
            };
 
            foreach (var room in rooms)
            {
                context.Rooms.Add(room);
            }
 
            var bookingStatuses = new List<BookingStatus>()
            {
                new BookingStatus { Id = 0, Title = "New" },
                new BookingStatus { Id = 1, Title = "Confirmed" },
                new BookingStatus { Id = 2, Title = "Arrived" },
                new BookingStatus { Id = 3, Title = "Checked Out" }
            };
 
            foreach (var status in bookingStatuses)
            {
                context.BookingStatuses.Add(status);
            }
 
            var bookings = new List<Booking>
            {
                new Booking { Name = "William T. Gleason", StartDate = DateTime.Now.AddDays(-4), EndDate = DateTime.Now.AddDays(3), IsPaid = true, BookingStatus = bookingStatuses[1], Room = rooms[4] }
            };
 
            foreach (var booking in bookings)
            {
                context.Bookings.Add(booking);
            }
 
            context.SaveChanges();
        }
    }

Now we should include the new db initializer to RoomBookingContext class:

public class RoomBookingContext : DbContext
    {
        public RoomBookingContext()
        {
            Database.SetInitializer(new DbInitializer());
        }
 
        public DbSet<Room> Rooms { get; set; }
        public DbSet<RoomStatus> RoomStatuses { get; set; }
        public DbSet<RoomType> RoomTypes { get; set; }
 
        public DbSet<Booking> Bookings { get; set; }
        public DbSet<BookingStatus> BookingStatuses { get; set; } 
    }

Add “Repository” class with implemented repository pattern:

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private RoomBookingContext _context;
 
        public Repository()
        {
            _context = new RoomBookingContext();
        }
 
        public Repository(RoomBookingContext context)
        {
            _context = context;
        } 
 
        public void Create(TEntity entity)
        {
            _context.Set<TEntity>().Add(entity);
            _context.SaveChanges();
        }
 
        public TEntity Read(int id)
        {
            return _context.Set<TEntity>().Find(id);
        }
 
        public void Update(TEntity entity)
        {
            _context.Set<TEntity>().Attach(entity);
            _context.Entry(entity).State = EntityState.Modified;
            _context.SaveChanges();
        }
 
        public void Delete(int id)
        {
            var set = _context.Set<TEntity>();
            var entity = set.Find(id);
            set.Remove(entity);
            _context.SaveChanges();
        }
 
        public IEnumerable<TEntity> ReadWhere(Func<TEntity, bool> predicate)
        {
            return _context.Set<TEntity>().Where(predicate);
        } 
 
        public IEnumerable<TEntity> ReadAll()
        {
            return _context.Set<TEntity>().AsEnumerable();
        } 
    }

ASP.NET MVC project

Basics

This is quite a complex task, so if we want to use all scheduler customization responsibilities, we will make some additional scheduler component initialization by JavaScript on the client side. Firstly, let’s create ASP.NET MVC5 Empty project called “HotelRoomBooking.Presentation.Mvc5”.

 room booking project in mvc

room booking in mvc tutorial

Set Presentation.MVC5 as a startup project. Then install Scheduler.NET, Entity Framework and JSON.NET packages by NuGet:

PM> Install-Package DHTMLX.Scheduler.NET -ProjectName HotelRoomBooking.Presentation.Mvc5

PM> Install-Package EntityFramework -ProjectName HotelRoomBooking.Presentation.Mvc5

PM> Install-Package Newtonsoft.Json -ProjectName HotelRoomBooking.Presentation.Mvc5

Add references on all other solution projects (Infrastructure, Entities and DataAccess).

Overload basic routing settings in AppStart/RouteConfig.cs file:

public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Calendar", action = "Index", id = UrlParameter.Optional }
            );
        }
    }

Let’s create basic MVC structure for our project (consists of folders and files).

Create “Content” folder with empty “Site.css” file. Add empty “scheduler_initialization.js” file to the “Scripts” folder. Set Views folder structure:

*web.config was created automatically

_ViewStart.cshtml content:

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

_Layout.cshtml content

<html>
<head>
    <title>@ViewBag.Title</title>
</head>
<body>
    @RenderBody()
</body>
</html>

Calendar controller

In this controller we will set scheduler initialization settings that will be translated into JavaScript on the client-side. Create empty CalendarController in Controllers folder.

In Index( … ) action result method we will initialize scheduler via Scheduler.NET and transfer model into view to render.

Add private configured timeline view returning method to the current controller:

private TimelineView ConfigureTimelineView(IEnumerable<object> rooms)
        {
            var timeline = new TimelineView("Timeline", "room_number");
            timeline.RenderMode = TimelineView.RenderModes.Bar;
            timeline.X_Unit = TimelineView.XScaleUnits.Day;
            timeline.X_Date = "%d";
            timeline.X_Size = 45;
            timeline.AddSecondScale(TimelineView.XScaleUnits.Month, "%F %Y");
            timeline.Dy = 51;
            timeline.SectionAutoheight = false;
            timeline.RoundPosition = true;
 
            timeline.FullEventDy = true;
 
            timeline.ServerList = "Rooms";
 
            timeline.AddOptions(rooms);
 
            return timeline;
        }

Private method that colors weekends in other color in the scheduler:

private void MarkWeekends(DHXScheduler scheduler)
        {
            string cssClass = "timeline_weekend";
            scheduler.TimeSpans.Add(new DHXMarkTime { Day = DayOfWeek.Saturday, CssClass = cssClass});
            scheduler.TimeSpans.Add(new DHXMarkTime {Day = DayOfWeek.Sunday, CssClass = cssClass});
        }

Add configuring lightbox (new booking addition form) method:

private void ConfigureLightBox(DHXScheduler scheduler, IEnumerable<object> rooms)
        {
            var name = new LightboxText("text", "Name");
            name.Height = 24;
            scheduler.Lightbox.Add(name);
 
            var roomsSelect = new LightboxSelect("room_number", "Room");
            roomsSelect.AddOptions(rooms);
            scheduler.Lightbox.Add(roomsSelect);
 
            var status = new LightboxRadio("status", "Status");
            var statuses = new Repository<BookingStatus>().ReadAll().Select(s => new {key = s.Id, label = s.Title});
            status.AddOptions(statuses);
            scheduler.Lightbox.Add(status);
            scheduler.InitialValues.Add("status", statuses.First().key);
 
            var isPaid = new LightboxCheckbox("is_paid", "Paid");
            scheduler.Lightbox.Add(isPaid);
            scheduler.InitialValues.Add("is_paid", false);
 
            var date = new LightboxMiniCalendar("time", "Time");
            scheduler.Lightbox.Add(date);
 
 
            scheduler.InitialValues.Add("text", String.Empty);
        }

Add general private method that will configure scheduler:

private DHXScheduler ConfigureScheduler()
        {
            var scheduler = new DHXScheduler();
 
 
            ViewBag.RoomTypes = JsonConvert.SerializeObject(new Repository<RoomType>().ReadAll());
            ViewBag.RoomStatuses = JsonConvert.SerializeObject(new Repository<RoomStatus>().ReadAll());
            ViewBag.BookingStatuses = JsonConvert.SerializeObject(new Repository<BookingStatus>().ReadAll());
 
            scheduler.Extensions.Add(SchedulerExtensions.Extension.Limit);
            scheduler.Extensions.Add(SchedulerExtensions.Extension.Collision);
            scheduler.Config.collision_limit = 1;
 
            var rooms = new Repository<Room>().ReadAll().Select(
                r => new
                {
                    key = r.Id,
                    room_number = r.Number,
                    label = r.Number,
                    type = r.RoomTypeId,
                    status = r.RoomStatusId
                }).OrderBy(r => r.room_number).ToList();
 
            scheduler.Skin = DHXScheduler.Skins.Flat;
 
            var timeLine = ConfigureTimelineView(rooms);
            scheduler.Views.Clear();
            scheduler.Views.Add(timeLine);
            scheduler.InitialView = timeLine.Name;
 
            scheduler.BeforeInit.Add("pre_init();");
 
            ConfigureLightBox(scheduler, rooms);
 
            MarkWeekends(scheduler);
 
            return scheduler;
        }

Notes:

ServerList - name of collection of JavaScript for objects that is used in multiple parts of scheduler. It will be generated automatically in our case.

The code below will transfer JSON to the view to share these entities with the client side:

ViewBag.RoomTypes = JsonConvert.SerializeObject(new Repository<RoomType>().ReadAll());
            ViewBag.RoomStatuses = JsonConvert.SerializeObject(new Repository<RoomStatus>().ReadAll());
            ViewBag.BookingStatuses = JsonConvert.SerializeObject(new Repository<BookingStatus>().ReadAll());
 
scheduler.Extensions.Add(SchedulerExtensions.Extension.Limit); - adds red today line on the view;
 
scheduler.Extensions.Add(SchedulerExtensions.Extension.Collision); - checks collision on the client-side

Rooms will be transferred to the server automatically on initial data loading, because it was used in lightbox (Scheduler.NET feature).

Code below will execute JavaScript pre initialization function that will be described later

scheduler.BeforeInit.Add("pre_init();");

Add Index ( … ) method and generate an empty view for it:

public ActionResult Index()
        {
            return View(ConfigureScheduler());
        }

View (Views / Calendar / Index.cshtml)

@model DHTMLX.Scheduler.DHXScheduler
 
<link rel="stylesheet" href="~/Content/Site.css"/>
<script src="~/Scripts/scheduler_initialization.js"></script>
<script>
    var roomTypesCollection = @Html.Raw(ViewBag.RoomTypes);
    var roomStatusesCollection = @Html.Raw(ViewBag.RoomStatuses);
    var bookingStatusesCollection = @Html.Raw(ViewBag.BookingStatuses);
</script>
<div style="width: 100%; height: 600px; margin: 0 auto;">
    @Html.Raw(@Model.Render())
</div>

Gray marked text will initialize JavaScript collections that were described previously. scheduler_initialization.js file will be described below.

Client-side scheduler initialization

Created scheduler_initialization.js file will contain additional client-side scheduler initialization. It will have post-, pre-initialization and some help functions.

Help functions

Firstly, let’s create help functions:

// returns title of room types, room statuses and booking statuses
// collections were predefined on view:
//  var roomTypesCollection
//  var roomStatusesCollection
//  var bookingStatusesCollection
function titleById(collection, id) {
    for (var i = 0; i < collection.length; i++) {
        if (collection[i].Id == id) {
            return collection[i].Title;
        }
    }
 
    return "";
}
 
function formatDateForEventBox(date) {
        var formatFunc = scheduler.date.date_to_str("%d %M %Y");
        return formatFunc(date);
    }

Pre-initialization function

Now let’s write and fill pre_init() function. It will be executed before scheduler rendering.

Set room item template (CSS will be described later):

    scheduler.templates.Timeline_scale_label = function (key, label, room) {
 
        var indicatorClass = "room_status_indicator_" + room.status;
 
        var template = "<div class='timeline_item_separator'></div>" +
            "<div class='timeline_item_cell'>" + room.room_number + "</div>" +
            "<div class='timeline_item_separator'></div>" +
            "<div class='timeline_item_cell'>" + titleById(roomTypesCollection, room.type) + "</div>" +
            "<div class='timeline_item_separator'></div>" +
            "<div class='timeline_item_cell room_status'>" + "<span class='room_status_indicator " + indicatorClass + "'></span>" + "<span>" + titleById(roomStatusesCollection, room.status) + "</span>" + "</div>";
 
        return template;
    }

Set 1 month switch step:

    scheduler.date.Timeline_start = scheduler.date.month_start;
    scheduler.date.add_Timeline = function (date, step) {
        return scheduler.date.add(date, step, "month");
    };
 
scheduler.attachEvent("onBeforeViewChange", function (old_mode, old_date, mode, date) {
        var year = date.getFullYear();
        var month = (date.getMonth() + 1);
        var d = new Date(year, month, 0);
        var daysInMonth = d.getDate();
        scheduler.matrix["Timeline"].x_size = daysInMonth;
        return true;
    });

Add events coloring:

scheduler.templates.event_class = function (start, end, event) {
        return "event_" + (event.status || "");
    }

Paid and status properties displaying for events:

scheduler.templates.event_bar_text = function (start, end, event) {
        var paidStatus = event.is_paid == true ? "paid" : "not paid";
        var startDate = formatDateForEventBox(event.start_date);
        var endDate = formatDateForEventBox(event.end_date);
        var dates = startDate + " - " + endDate;
        var statusDiv = "<div class='booking_status booking-option'>" + (titleById(bookingStatusesCollection, event.status) || "") + "</div>";
        var paidDiv = "<div class='booking_paid booking-option'>" + paidStatus + "</div>";
 
        var output = event.text + "<br />" + dates + statusDiv + paidDiv;
        return output;
    };

Client-side collision message display definition:

scheduler.attachEvent("onEventCollision", function (ev, evs) {
 
        for (var i = 0; i < evs.length; i++) {
 
            if (ev.room_number != evs[i].room_number) continue;
 
            dhtmlx.message({
                type: "error",
                text: "This room is already booked for this date."
            });
        }
 
        return true;
    });

Add rooms description definition:

var element = document.getElementById("scheduler_here");
    var top = scheduler.xy.nav_height + 1 + 1; // first +1 -- blank space upper border, second +1 -- hardcoded border length
    var height = scheduler.xy.scale_height * 2;
    var width = scheduler.matrix.Timeline.dx;
    var descriptionHTML = "<div class='timeline_item_separator'></div>" +
        "<div class='timeline_item_cell'>Number</div>" +
        "<div class='timeline_item_separator'></div>" +
        "<div class='timeline_item_cell'>Type</div>" +
        "<div class='timeline_item_separator'></div>" +
        "<div class='timeline_item_cell room_status'>Status</div>";
    descriptionHTML = "<div style='position: absolute; top: " + top + "px; width: " + width + "px; height: " + height + "px;'>" + descriptionHTML + "</div><div style='clear: both;'></div>";
    element.innerHTML += descriptionHTML;

Styles

Here we will fill Site.css file in Content folder.

Add:

html, body {
    font-family: "Segoe UI";
}

Remove timeline button:

.dhx_cal_tab_standalone {
    display: none;
}

Styles for timeline sidebar for rooms:

.timeline_item_cell {
    float: left;
    width: 32%;
    height: 100% !important;
    font-size: 12px;
    text-align: center;
    line-height: 50px;
}
 
.room_status {
    position: relative;
}
 
.timeline_item_separator {
    float: left;
    background-color: #CECECE;
    width: 1px;
    height: 100% !important;
}
 
.room_status_indicator {
    position: absolute;
    background-color: red;
    left: 0;
    top: 0;
    right: 95%;
    bottom: 0;
}
 
.room_status_indicator_1 {
    background-color: #4CAF50;
}
 
.room_status_indicator_2 {
    background-color: red;
}
 
.room_status_indicator_3 {
    background-color: #FFA000;
}

Weekend highlighting:

.timeline_weekend {
    background-color: #FFF9C4;
}

Override default color on booking creation:

.dhx_cal_event_line {
    background-color: #FFB74D !important;
}

Set booking colors that will depend on status id:

.event_1 {
    background-color: #FFB74D !important;
}
 
.event_2 {
    background-color: #9CCC65 !important;
}
 
.event_3 {
    background-color: #40C4FF !important;
}
 
.event_4 {
    background-color: #BDBDBD !important;
}

Some booking block content members:

.booking_status {
    position: absolute;
    top: 2px;
    right: 2px;
}
 
.booking_paid {
    position: absolute;
    bottom: 2px;
    right: 2px;    
}

Removing dotted divs inside the booking block (legacy fix)

.dhx_cal_event_line:hover .booking-option {
    background: none !important;
}

Hide lightbox time (except date)

.dhx_section_time select {display:none}

Insert lightbox legacy conflict resolving:

.dhx_mini_calendar .dhx_year_week,
.dhx_mini_calendar .dhx_scale_bar{
    height: 30px !important;
}


Styles for aligning Time control panel in the LightBox form to the left:

.dhx_cal_light_wide .dhx_section_time {
    text-align: left;
}
.dhx_cal_light_wide .dhx_section_time > input:first-child {
    margin-left: 10px;
}
 
.dhx_cal_light_wide .dhx_section_time input{
    border: 1px solid #aeaeae;
    padding-left:5px;
}

Data access controller

Now the scheduler is configured but doesn’t send or load any data to the server on actions with bookings. So, it’s time to fix it.

Data loading

Let’s create DataAccessController empty controller in Controllers folder: Our application will load bookings dynamically, so bookings data returning method will receive time period dates as parameters from HTTP response. Add method to the controller:

public ActionResult Bookings()
        {
            var dateFrom = DateTime.ParseExact(this.Request.QueryString["from"], "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
            var dateTo = DateTime.ParseExact(this.Request.QueryString["to"], "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
 
            var bookings = new Repository<Booking>().ReadAll().Where(e => e.StartDate <= dateTo && e.EndDate >= dateFrom);
            return new SchedulerAjaxData(bookings);
        }

Then enable data loading for scheduler: add this code to ConfigureScheduler( … ) in CalendarController (insert before “return scheduler”).

scheduler.EnableDynamicLoading(SchedulerDataLoader.DynamicalLoadingMode.Month);
            scheduler.Config.show_loading = true;
            scheduler.LoadData = true;
            scheduler.DataAction = Url.Action("Bookings", "DataAccess");
scheduler.EnableDynamicLoading(SchedulerDataLoader.DynamicalLoadingMode.Month); 

- enables loading of bookings of current month (optimization)

Now our application load bookings from db.

Saving bookings to db

Now we will implement data saving on changes on the client-side.

Save( … ) method in DataAccessController will receive data from the client for update:

public ActionResult Save(int? id, FormCollection actionValues)
        {
            var action = new DataAction(actionValues);
            var repo = new Repository<Booking>();
 
            try
            {
                var changedBooking = DHXEventsHelper.Bind<Booking>(actionValues);
 
                switch (action.Type)
                {
                    case DataActionTypes.Insert:
                        repo.Create(changedBooking);
                        break;
                    case DataActionTypes.Delete:
                        repo.Delete(Convert.ToInt32(action.SourceId));
                        break;
                    case DataActionTypes.Update:
                        repo.Update(changedBooking);
                        break;
                }
                action.TargetId = changedBooking.Id;
            }
            catch (Exception e)
            {
                action.Type = DataActionTypes.Error;
            }
 
            return new AjaxSaveResponse(action);
        }

Code string

var changedBooking = DHXEventsHelper.Bind<Booking>(actionValues);

in this method composing entity from HTTP POST parameters.

Add strings below (insert to ConfigureScheduler( … ) method of CalendarController before “return scheduler” ) to enable data saving to the scheduler:

scheduler.EnableDataprocessor = true;
            scheduler.SaveAction = Url.Action("Save", "DataAccess");

Now scheduler can save and load data from db.

Extra features

Server-side collision

If several users will work with our application simultaneously, they could assign different bookings to one time, because we have only client-side collision check. So, we can check collision on server and alarm current user if somebody else have assigned another booking to the selected time. All this will work on page refresh.

Create collision check helper method in DataAccessController:

private bool IsCollidesWithOthers(Booking booking, IRepository<Booking> repo)
        {
            var collidedBookings =
                new Repository<Booking>().ReadWhere(
                    b => b.Id != booking.Id && b.RoomId == booking.RoomId && b.StartDate < booking.EndDate && b.EndDate > booking.StartDate);
 
            if (collidedBookings.Any()) return true;
 
            return false;
        }

Add collision check in Save ( … ) method (after var changedBooking = DHXEventsHelper.Bind<Booking>(actionValues); ):

                if (action.Type != DataActionTypes.Delete && IsCollidesWithOthers(changedBooking, repo))
                {
                    action.Type = DataActionTypes.Error;
                    action.Message = "This room is already booked for this date.";
 
                    return new AjaxSaveResponse(action);
                }

Action type check is necessary for situation when user could find unforeseen method of assigning 2 bookings to one time. It will give ability to avoid collision check to delete some booking.

Now we should add error displaying and data reloading on the client-side. Firstly, insert post_init( ) function to scheduler_initialization.js file:

function post_init() {
 
    scheduler.dataProcessor.attachEvent("onAfterUpdate", function (id, action, tid, response) {
        if (action == "error") {
 
            dhtmlx.message({
                type: "error",
                text: response.textContent || response.nodeValue
            });
 
            scheduler.clearAll();
            scheduler.load(dataActionUrl, "json"); // dataActuionUrl receiving from ViewBag.DataAction in the beginning of the view
        }
    });
}

dataActionUrl will be initialized in the top of the view and shared from the server by ViewBag. Add code below to ConfigureScheduler ( … ) of CalendarController (before “return scheduler”):

ViewBag.DataAction = scheduler.DataAction;

Next, initialize dataAction url in the Index.cshtml:

@model DHTMLX.Scheduler.DHXScheduler
 
<link rel="stylesheet" href="~/Content/Site.css" />
<script src="~/Scripts/scheduler_initialization.js"></script>
 
<script>
    var dataActionUrl = "@ViewBag.DataAction";
 
    var roomTypesCollection = @Html.Raw(ViewBag.RoomTypes);
    var roomStatusesCollection = @Html.Raw(ViewBag.RoomStatuses);
    var bookingStatusesCollection = @Html.Raw(ViewBag.BookingStatuses);
</script>
 
<div style="width: 100%; height: 600px; margin: 0 auto;">
    @Html.Raw(@Model.Render())
</div>

Set post_init( ) as post initialization function to scheduler: insert string below to ConfigureScheduler ( … ) of CalendarController (before “return scheduler”).

scheduler.AfterInit.Add("post_init();");

Done. Hotel room booking calendar is ready to use.

room booking reservation calendar

You can download sample of a hotel room booking calendar ready for use.


comments powered by Disqus