Hope you have already had time to check our new car rental app demo displaying how DHTMLX Scheduler .NET web control can be modified to implement a car rental application with a search field and a customer’s order form.

As promised, we elaborated the most precise tutorial to let you create a car rental sample for ASP.NET MVC3 in the shortest possible time.

The tutorial covers the essential steps of the new app creation and includes descriptions, pictures and full code examples.

For your convenience, we divided the whole tutorial into three parts:



Implementation of the Main Functionality; Creation of Date and Time Filter; Signing Rent Boxes with Rent Duration Period;

After completing the above mentioned steps, you’ll get a ready car rental service like on the picture below:

You can skip reading this tutorial and simply download a ready package right now.

Part I. Implementation of the Main Functionality

Let’s create a simple application with a calendar that has a vertical car arrangement and a search field to filter the available cars by price and type.



a) Initialization



As the first step, we create a new ASP.NET MVC3 Web Application with the Razor view engine.

For initialization add the .dll library reference and scripts from the DHTMLX Scheduler .NET package to your project folder. If you need help with that, please, refer to the items 2-3 of our ASP.NET simple event calendar tutorial.



b) Database and Model Creation



Right-click on your project name in the Solution Explorer and add a new ASP.NET folder App_Data. Now create a new SQL Server Database and name it MyScheduler.mdf

To set up the application database create 3 tables – Type,Car and Order – and add the required columns:



The ‘Type’ table should only have the car id and the title values:

The ‘Car’ table should include data to identify cars price, brand, type and image:

Let’s make the TypeId field a foreign key that refers to [Type].id field.



The ‘Order’ table is used to enable order placement with a car rental form. Therefore it should contain description, time period, pick up/drop off locations and car id columns:

Like with the ‘Car’, table make’ the Car_id field a foreign key that refers to [Car].id field.

Set primary key to the id columns of the three tables. Remember to change the properties of the identity column to id.



When the tables are completed, create a data model LINQ to SQL Classes. Name it Rental and drag the newly created tables onto the LINQ to SQL designer surface.

To facilitate rendering of the Order collection as a json string during data loading, no cyclic links between ‘Order’ and ‘Type’ should be set.



To achieve this, change Parent Property Access from Public to Internal for each association:

Go to Views, create a new folder ‘Home’ and add Index.cshtml. Delete the views we do not require from the folders Home and Shared, that’s Error.cshtml and About.cshtml.

Right-click on the ‘Models’ to create a ViewModel.cs with the Scheduler object and a category (car) count:

using System; using System.Collections.Generic; using System.Linq; using System.Web; using DHTMLX.Scheduler; namespace Rental.Models { public class ViewModel { public DHXScheduler Scheduler { get; set; } public int CategoryCount { get; set; } }

Output data generated with the car search form are stored in the FormState:

public class FormState { public string Type { get; set; } public string Price { get; set; } }

To change our website layout go to Views -> Shared -> _Layout.cshtml :

<!DOCTYPE html> <html> <head> <title>@ViewBag.Title</title> <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" /> <script src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")" type="text/javascript"></script> </head> <body> <div class="page"> <div id="header"> </div> <div id="main"> @RenderBody() <div id="footer"> </div> </div> </div> </body> </html>

Go to Home -> Index.cshtml to make changes also to the calendar page.



The full code at this stage should look as follows:

@{ ViewBag.Title = "Car Rental Service"; } @model Rental.Models.ViewModel @Html.Raw(Model.Scheduler.GenerateCSS()) @Html.Raw(Model.Scheduler.GenerateJS()) <style type="text/css"> /*override some scheduler css*/ #scheduler_here .dhx_cal_tab { display:none; } .dhx_cal_header div div { border:none; } .dhx_cal_data { background-color:#fff; } .dhx_cal_event_line { background-image:none; background-color:#FFDD71; font-size:13px; line-height:@((Model.Scheduler.Views[0] as DHTMLX.Scheduler.Controls.TimelineView).EventDy)px; } .dhx_matrix_scell { max-width:@((Model.Scheduler.Views[0] as DHTMLX.Scheduler.Controls.TimelineView).Dx)px; } .dhx_cal_event_line .dhx_event_resize { background-image: none; } </style> <script> //define template for timeline units function def_template(){ scheduler.templates['@((Model.Scheduler.Views[0]).Name)_scale_label']= function(key, label, obj){ return "<div style=\"width:100%\">\ <img src=\""+obj.link+"\" alt=\""+label+"\"></img><br/>\ <div class=\"car_brand\">"+label+"</div><div class=\"car_price\">$"+obj.price+"</div></div>"; }; scheduler.templates.event_bar_text = function (start, end, event) { return "Rented"; }; } </script> <div style="height:750px"> <div class="message" > @ViewBag.Message </div> <div style="float:left; width:230px;height:100%;"> @using (Html.BeginForm("Index", "Home", FormMethod.Post)) { <div class="search_form"> <div class="form_head"> <span class="rent_title">Rent a Car</span><br /> <span class="rent_small">with DHTMLX Scheduler .NET</span><br /> <div class="hd_line"></div> </div> <div class="controls"> <div> @Html.Label("Type", "Type:")<br /> @Html.DropDownList("Type") </div> <div> @Html.Label("Price", "Price:")<br /> @Html.DropDownList("Price") </div> <div> <input type="submit" id="submit" value="Search" /> </div> </div> </div> } </div> <div style="float:left; width:950px;height:100%;"> @if (!string.IsNullOrEmpty((string)ViewData["Message"] )) { <script> dhtmlx.message("@ViewData["Message"]"); </script> } @{ //calculate height of calendar container int rowHeigth = (Model.Scheduler.Views[0] as DHTMLX.Scheduler.Controls.TimelineView).Dy; int headerHeight = 45; int showRows = 7; int actualHeight = rowHeigth * Model.CategoryCount + headerHeight; int maxHeight = showRows * rowHeigth + headerHeight; } <div style="height:@(actualHeight < maxHeight ? actualHeight : maxHeight)px;"> @Html.Raw(Model.Scheduler.GenerateHTML()) </div> </div> <div class="clear"></div> <script> //after created/edited rent order - navigate calendar to its start date scheduler.attachEvent("onEventSave", function (id, data, is_new_event) { if (data.start_date) { scheduler.setCurrentView(new Date(data.start_date)); } return true; }); </script> </div>

Below you can find detailed descriptions of how we implement Scheduler .NET rendering and initialization as well as customize the calendar template :

1. Scheduler .NET rendering



This time we render the Scheduler in an unusual way to override some default css styles after loading and before Scheduler initialization. Instead of using Scheduler.Render () we did in the following way:



At first, we loaded the required css and js:

@Html.Raw(Model.Scheduler.GenerateCSS()) @Html.Raw(Model.Scheduler.GenerateJS())

And finalized it with generating the markup and initialization code:

@Html.Raw(Model.Scheduler.GenerateHTML())

2. Scheduler .NET initialization



The Scheduler initializes in the container of the set height. The number of cars (and consequently, the actual calendar height) can be different. It depends on the search form output. We’ve chosen the default height of 7 lines. If there are < 7 lines, the calendar height is equal to the number of lines. If there are > 7 lines in the calendar, scrolling is enabled while the calendar height is set to the default 7 lines.

@{

//calculate height of calendar container int rowHeigth = (Model.Scheduler.Views[0] as DHTMLX.Scheduler.Controls.TimelineView).Dy; int headerHeight = 45; int showRows = 7; int actualHeight = rowHeigth * Model.CategoryCount + headerHeight; int maxHeight = headerHeight + showRows * rowHeigth; } <div style="height:@(actualHeight < maxHeight ? actualHeight : maxHeight)px;">

3. Scheduler .NET template customization

We override templates for the car scale and rent boxes:

scheduler.templates['@((Model.Scheduler.Views[0]).Name)_scale_label']= function(key, label, obj){ return "<div style=\"width:100%\">\ <img src=\""+obj.link+"\" alt=\""+label+"\"></img><br/>\ <div class=\"car_brand\">"+label+"</div><div class=\"car_price\">$"+obj.price+"</div></div>"; }; scheduler.templates.event_bar_text = function (start, end, event) { return "Rented"; };

Proceed with adding the required classes to Site.css:

body { background: #5c87b2; background-image: url("./background_car_rent.png"); font-size: 75%; font-family:Arial,sans-serif; margin: 0; padding: 0; color: #696969; } .hd_line { height:1px; background-image: url("./line.png"); width:100%; margin:10px 0 20px; } .rent_title { color: #d4f4fc; font-size:18px; } .rent_small { color: #d4e6fc; font-size:10px; } .search_form { width:173px; height:350px; padding:15px 20px; background-repeat:repeat-x; background-image: url("./bg_form.png"); } .message { text-align:right; color:White; font-size:18px; height:24px; padding:5px; } .car_brand, .car_price { border-left:none !important; overflow:hidden; } .car_price { color:Gray; width:100%; font-size:10px; margin-top:2px; text-align:center; } .car_brand { white-space:nowrap; margin-top:-4px; color:Black; font-weight:bold; } .search_form input { height:18px; } .search_form select { height:19px; vertical-align:top; } .search_form option { vertical-align:middle; } .search_form select, .search_form input { font-size:11px; width:170px; padding: 0; margin: 5px 0; } #submit { width: 170px; height: 23px; border: 0 none; background-color: #EFEFEF; line-height: 23px; margin-top: 15px; font-weight:bold; } .search_form { border:none; color:#eee; } .search_form .controls > div { margin-top:9px; } /* PRIMARY LAYOUT ELEMENTS ----------------------------------------------------------*/ /* you can specify a greater or lesser percentage for the page width. Or, you can specify an exact pixel width. */ .page { width: 90%; margin-left: auto; margin-right: auto; } #main { padding: 30px 30px 15px 30px; _height: 1px; /* only IE6 applies CSS properties starting with an underscore */ } .clear { clear: both; } .page { width:1200px; } #main { padding:0; } .minical_container { position:absolute; width:200px; }

c) Create a controller



The next stage is to create a controller. Right-click on the Controllers folder in the Solution Explorer and create HomeController.cs.



The full code should look like this:

using System.Linq; using System.Web; using System.Web.Mvc; using System.Collections; using System.Collections.Generic; using DHTMLX.Scheduler; using DHTMLX.Scheduler.Controls; using DHTMLX.Scheduler.Data; using DHTMLX.Common; using Rental.Models; namespace Rental.Controllers { public class HomeController : Controller { public ActionResult Index(FormState state) { var scheduler = new DHXScheduler(this); scheduler.Extensions.Add(SchedulerExtensions.Extension.Collision); scheduler.Extensions.Add(SchedulerExtensions.Extension.Minical); //call custom template initialization scheduler.BeforeInit.Add("def_template();"); scheduler.Config.time_step = 60; //set row height scheduler.XY.bar_height = 76; scheduler.InitialValues.Add("text", ""); var context = new RentalDataContext(); //selecting cars according to form values var cars = _SelectCars(context, state); //if no cars found - show message and load default set if (cars.Count() == 0) { ViewData["Message"] = "Nothing was found on your request"; cars = _SelectCars(context);//select default set of events } //create custom details form var printableList = cars.Select(c => new { key = c.id, label = c.Brand, price = c.Price, link = c.Photo }); _ConfigureLightbox(scheduler, printableList); //load cars to the timeline view _ConfigureViews(scheduler, printableList); //assign ViewData values _UpdateViewData(scheduler, context, state); //data loading/saving settings scheduler.PreventCache(); scheduler.LoadData = true; scheduler.EnableDataprocessor = true; //collect model var model = new ViewModel(); model.Scheduler = scheduler; model.CategoryCount = cars.Count(); return View(model); } protected void _UpdateViewData(DHXScheduler scheduler, RentalDataContext context, FormState state) { ViewData["Price"] = _CreatePriceSelect(scheduler, state.Price); ViewData["Type"] = _CreateTypeSelect(context.Types, state.Type); } private List<SelectListItem> _CreateTypeSelect(IEnumerable<Models.Type> types, string selected) { var typesList = new List<SelectListItem>() { new SelectListItem(){Value = "", Text = "Any"} }; foreach (var type in types) { var item = new SelectListItem() { Value = type.id.ToString(), Text = string.Format("{0}: {1} cars", type.title, type.Cars.Count) }; if (item.Value == selected) item.Selected = true; typesList.Add(item); } return typesList; } private List<SelectListItem> _CreatePriceSelect(DHXScheduler scheduler, string selected) { var priceRanges = new string[] { "50-80", "80-120", "120-150" }; var prices = new List<SelectListItem>(){ new SelectListItem(){Value = "", Text = "Any"} }; foreach (var pr in priceRanges) { var item = new SelectListItem() { Value = pr, Text = string.Format("${0}", pr) }; if (pr == selected) item.Selected = true; prices.Add(item); } return prices; } protected IQueryable<Car> _SelectCars(RentalDataContext context) { return _SelectCars(context, null); } protected IQueryable<Car> _SelectCars(RentalDataContext context, FormState state) { IQueryable<Car> cars = from car in context.Cars select car; if (state == null) return cars; string _type = state.Type; string _price = state.Price; //filter by car type if (!string.IsNullOrEmpty(_type)) { int type = context.Types.First().id; if (!string.IsNullOrEmpty(_type)) { int.TryParse(_type, out type); } cars = cars.Where(c => c.TypeId == type); } //filter by price if (!string.IsNullOrEmpty(_price)) { var price = _price.Split('-'); int low = int.Parse(price[0]); int top = int.Parse(price[1]); cars = cars.Where(c => c.Price <= top && c.Price >= low); } return cars; } protected void _ConfigureViews(DHXScheduler scheduler, IEnumerable cars) { var units = new TimelineView("Orders", "car_id"); units.X_Step = 2; units.X_Length = 12; units.X_Size = 12; //width of the first column units.Dx = 149; //row height units.Dy = 76; //rent boxes height units.EventDy = units.Dy - 5; units.AddOptions(cars); units.RenderMode = TimelineView.RenderModes.Bar; scheduler.Views.Clear(); scheduler.Views.Add(units); scheduler.InitialView = scheduler.Views[0].Name; } protected void _ConfigureLightbox(DHXScheduler scheduler, IEnumerable cars) { scheduler.Lightbox.Add(new LightboxText("text", "Contact details") { Height = 42, Focus = true }); scheduler.Lightbox.Add(new LightboxText("description", "Note") { Height = 63 }); var select = new LightboxSelect("car_id", "Car Brand"); scheduler.Lightbox.Add(select); scheduler.Lightbox.Add(new LightboxText("pick_location", "Pick up location") { Height = 21 }); scheduler.Lightbox.Add(new LightboxText("drop_location", "Drop off location") { Height = 21 }); select.AddOptions(cars); scheduler.Lightbox.Add(new LightboxTime("time", "Time period")); } public ContentResult Data() { return new SchedulerAjaxData((new RentalDataContext()).Orders); } public ContentResult Save(int? id, FormCollection actionValues) { var action = new DataAction(actionValues); RentalDataContext data = new RentalDataContext(); try { var changedEvent = (Order)DHXEventsHelper.Bind(typeof(Order), actionValues); switch (action.Type) { case DataActionTypes.Insert: data.Orders.InsertOnSubmit(changedEvent); break; case DataActionTypes.Delete: changedEvent = data.Orders.SingleOrDefault(ev => ev.id == action.SourceId); data.Orders.DeleteOnSubmit(changedEvent); break; default:// "update" var eventToUpdate = data.Orders.SingleOrDefault(ev => ev.id == action.SourceId); DHXEventsHelper.Update(eventToUpdate, changedEvent, new List<string>() { "id" }); break; } data.SubmitChanges(); action.TargetId = changedEvent.id; } catch { action.Type = DataActionTypes.Error; } return (new AjaxSaveResponse(action)); } } }

Let’s have an insight into the most essential code snippets:



First of all, add ‘Collision extension’ to avoid multiple orders of one and the same car for the same period. At this step we also connect a mini-calendar that will be later required:

public ActionResult Index(FormState state) { var scheduler = new DHXScheduler(this); scheduler.Extensions.Add(SchedulerExtensions.Extension.Collision); scheduler.Extensions.Add(SchedulerExtensions.Extension.Minical);

It’s easy to implement filtration with LINQ to SQL. We simply add several where filters.

protected IQueryable<Car> _SelectCars(RentalDataContext context, FormState state) { IQueryable<Car> cars = from car in context.Cars select car; if (state == null) return cars; string _type = state.Type; string _price = state.Price;

It means, LINQ doesn’t filter the collection each time when we call Where(). LINQ to SQL will translate these conditions into the equivalent SQL query and send it to the SQL server for processing just before cars collection is enumerated.

//filter by car type if (!string.IsNullOrEmpty(_type)) { int type = context.Types.First().id; if (!string.IsNullOrEmpty(_type)) { int.TryParse(_type, out type); } cars = cars.Where(c => c.TypeId == type); } //filter by price if (!string.IsNullOrEmpty(_price)) { var price = _price.Split('-'); int low = int.Parse(price[0]); int top = int.Parse(price[1]); cars = cars.Where(c => c.Price <= top && c.Price >= low); } return cars; }

We set a 2 step time interval for car order with the scale of 12 cells in the TimelineView. Columns and rent boxes height as well as the height of rows are also set here. The function scheduler.Views.Clear(); removes the default scheduler views.

var units = new TimelineView("Orders", "car_id"); units.X_Step = 2; units.X_Length = 12; units.X_Size = 12; //width of the first column units.Dx = 149; //row height units.Dy = 76; //rent boxes height units.EventDy = units.Dy - 5; units.AddOptions(cars); units.RenderMode = TimelineView.RenderModes.Bar; scheduler.Views.Clear(); scheduler.Views.Add(units); scheduler.InitialView = scheduler.Views[0].Name;

Create a list of select options in the controller and pass them to view via ViewData. Retain the selected values in the controls after page reload.

protected void _UpdateViewData(DHXScheduler scheduler, RentalDataContext context, FormState state) { ViewData["Price"] = _CreatePriceSelect(scheduler , state.Price); ViewData["Type"] = _CreateTypeSelect(context.Types, state.Type); }

Let’s customize the customer’s form for order placement by adding the following code:

protected void _ConfigureLightbox(DHXScheduler scheduler, IEnumerable cars) { scheduler.Lightbox.Add(new LightboxText("text", "Contact details") { Height = 42, Focus = true }); scheduler.Lightbox.Add(new LightboxText("description", "Note") { Height = 63 }); var select = new LightboxSelect("car_id", "Car Brand"); scheduler.Lightbox.Add(select); scheduler.Lightbox.Add(new LightboxText("pick_location", "Pick up location") { Height = 21 }); scheduler.Lightbox.Add(new LightboxText("drop_location", "Drop off location") { Height = 21 }); select.AddOptions(cars); scheduler.Lightbox.Add(new LightboxTime("time", "Time period")); }

The form will look like the picture below:

Note: Save and Data methods we use in this app are described in the Simple ASP.NET MVC application with Scheduler.

Copy the available car images (image size is 80x48 px) to the Content folder of your project. Right-click on the database in the Server Explorer to create a new query. Add the car rental static data to the tables Cars and Types, and execute the SQL file. The data and the necessary pics are available in the download package.

The first part of the tutorial is completed. Now you have a nice-looking basic rental calendar in ASP.NET MVC3 Razor with vertically arranged cars and a simple price and time select:

Part II. Creation of Date and Time Filter

In the second part of the tutorial we create pick up/drop off date and time selects:

Open Index.cshtml and update it by adding the following time selects:

<div> <span>Pick Up Date:</span><br /> @Html.TextBox("DateFrom") <img src="@Url.Content("~/Content/calendar.gif")" class="date_calendar" onclick="show_minical('DateFrom');"/> @Html.DropDownList("TimeFrom") </div> <div> <span>Drop Off Date:</span><br /> @Html.TextBox("DateTo") <img src="@Url.Content("~/Content/calendar.gif")" class="date_calendar" onclick="show_minical('DateTo');"/> @Html.DropDownList("TimeTo") </div>

We also add a checkbox to enable/disable date filters:

<div class="check_dates"> <span>Only available: </span> @Html.CheckBox("DateFilter", true) </div>

Go back to Site.css and add styles for the new controls:

.check_dates { line-height:14px; margin-bottom:-10px; margin-top:0 !important; } .check_dates > input { width:20px; vertical-align:middle; } #DateFrom, #DateTo { width:80px; } #TimeFrom, #TimeTo { width:60px; } .date_calendar { cursor:pointer; height:18px; left:-2px; position:relative; vertical-align:baseline; top:1px; width:18px; }

Let’s define the date picker behavior. Go to Scripts folder and create scripts.js. Add a minical function for the automatic show/ remove of the date picker:

/* Date picker behavior */ scheduler.pickerDateFormat = "%m/%d/%Y"; function remove_minical() { if (scheduler._calendar) { scheduler.destroyCalendar(scheduler._calendar); $("#minical_cont").remove(); scheduler._calendar = null; } } function show_minical(id) { //if mini calendar already shown - destroy it if (scheduler._calendar) { remove_minical(); } //create container for the mini calendar var position = $("#" + id).position(); var div = $("<div></div>") .attr('id', 'minical_cont').attr('class', 'minical_container') .css('left', position.left).css('top', position.top); $('body').append(div); //create minicalendar scheduler._calendar = scheduler.renderCalendar({ container: "minical_cont", date: scheduler._date, navigation: true, handler: function (date, calendar) { //on click - put selected value to the input $("#" + id).val(scheduler.date.date_to_str(scheduler.pickerDateFormat)(date)); //and remove calendar remove_minical(); //check if 'To' date is later than 'From' date if (!areDatesCorrect()) { showWarning(); } } }); function areDatesCorrect() { var from = $("#DateFrom").val(), to = $("#DateTo").val(); if (from && to) { //function to convert string to date object var converter = scheduler.date.str_to_date(scheduler.pickerDateFormat); from = converter(from); to = converter(to); if (from && to) {//if converted successfully if (from.getTime() > to.getTime()) //return false only if start date is later than end date, other cases are valid return false; } } return true; } function showWarning() { $("#DateTo").val(""); dhtmlx.message("Pick up date must be before Drop off date!"); } }

Update Index.cshtml by connecting the JavaScript library as it is shown below:

<script src="@Url.Content("~/Scripts/scripts.js")" type="text/javascript"></script>

Now we can adjust the server-side application logic:



a) Model



Update ViewModel.cs by adding properties for the new controls to the FormState:

public class FormState { public string DateFrom { get; set; } public string DateTo { get; set; } public string TimeFrom { get; set; } public string TimeTo { get; set; } public string Type { get; set; } public string Price { get; set; } public bool DateFilter { get; set; } }

b) Controller and View



Date is selected from the two inputs – date picker and ‘select time’ drop-down list. We add a function to HomeController.cs to parse the date to the DataTime object.

private DateTime _ParseDate(string date, string time) { var datestr = string.Format("{0} {1}", date, time); DateTime result = new DateTime(); DateTime.TryParse(datestr, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out result); return result; }

If Pick Up Date is selected, we make it the calendar initial date:

public ActionResult Index(FormState state) { … if (_ParseDate(state.DateFrom, state.TimeFrom) != default(DateTime)) { scheduler.InitialDate = _ParseDate(state.DateFrom, state.TimeFrom); } … }

Let’s add one more filter to the method _SelectCars:

protected IQueryable<Car> _SelectCars(RentalDataContext context) { … var _from = default(DateTime); var _to = default(DateTime); //try to parse time range if (!string.IsNullOrEmpty(state.DateFrom)) { _from = _ParseDate(state.DateFrom, state.TimeFrom); _to = _ParseDate(state.DateTo, state.TimeTo); if (_from.CompareTo(default(DateTime)) != 0 && _to.CompareTo(default(DateTime)) == 0)//only start date set { _to = _from.AddHours(1); } } if (state.DateFilter && _from != default(DateTime) && _to != default(DateTime)) { //select cars, which are available in specified time range cars = from car in cars where car.Orders.Count == 0 || car.Orders.Where(o => o.end_date > _from && o.start_date < _to).Count() == 0 select car ; } return cars; }

Render the values of new controllers to ViewData:

protected void _UpdateViewData(DHXScheduler scheduler, RentalDataContext context, FormState state) { ViewData["Price"] = _CreatePriceSelect(scheduler , state.Price); ViewData["Type"] = _CreateTypeSelect(context.Types, state.Type) ; ViewData["DateFrom"] = state.DateFrom; ViewData["DateTo"] = state.DateTo; ViewData["TimeFrom"] = _CreateTimeSelect(scheduler, state.TimeFrom); ViewData["TimeTo"] = _CreateTimeSelect(scheduler, state.TimeTo); ViewData["DateFilter"] = state.DateFilter; }

Here is the full code:

using System; using System.Collections.Generic; using System.Collections; using System.Linq; using System.Web; using System.Web.Mvc; using DHTMLX.Scheduler; using DHTMLX.Scheduler.Controls; using DHTMLX.Scheduler.Data; using DHTMLX.Common; using Rental.Models; namespace Rental.Controllers { public class HomeController : Controller { public ActionResult Index(FormState state) { var scheduler = new DHXScheduler(this); scheduler.Extensions.Add(SchedulerExtensions.Extension.Collision); scheduler.Extensions.Add(SchedulerExtensions.Extension.Minical); //call custom template initialization scheduler.BeforeInit.Add("def_template();"); scheduler.Config.time_step = 60; //set row height scheduler.XY.bar_height = 76; scheduler.InitialValues.Add("text", ""); //if 'Pick Up Date' selected - make it initial date for calendar if (_ParseDate(state.DateFrom, state.TimeFrom) != default(DateTime)) { scheduler.InitialDate = _ParseDate(state.DateFrom, state.TimeFrom); } var context = new RentalDataContext(); //selecting cars according to form values var cars = _SelectCars(context, state); //if no cars found - show message and load default set if (cars.Count() == 0) { ViewData["Message"] = "Nothing was found on your request"; cars = _SelectCars(context);//select default set of events } //create custom details form var printableList = cars.Select(c => new { key = c.id, label = c.Brand, price = c.Price, link = c.Photo }); _ConfigureLightbox(scheduler, printableList); //load cars to the timeline view _ConfigureViews(scheduler, printableList); //assign ViewData values _UpdateViewData(scheduler, context, state); //data loading/saving settings scheduler.PreventCache(); scheduler.LoadData = true; scheduler.EnableDataprocessor = true; //collect model var model = new ViewModel(); model.Scheduler = scheduler; model.CategoryCount = cars.Count(); return View(model); } protected void _UpdateViewData(DHXScheduler scheduler, RentalDataContext context, FormState state) { ViewData["Price"] = _CreatePriceSelect(scheduler, state.Price); ViewData["Type"] = _CreateTypeSelect(context.Types, state.Type); ViewData["DateFrom"] = state.DateFrom; ViewData["DateTo"] = state.DateTo; ViewData["TimeFrom"] = _CreateTimeSelect(scheduler, state.TimeFrom); ViewData["TimeTo"] = _CreateTimeSelect(scheduler, state.TimeTo); ViewData["DateFilter"] = state.DateFilter; } private List<SelectListItem> _CreateTypeSelect(IEnumerable<Models.Type> types, string selected) { var typesList = new List<SelectListItem>() { new SelectListItem(){Value = "", Text = "Any"} }; foreach (var type in types) { var item = new SelectListItem() { Value = type.id.ToString(), Text = string.Format("{0}: {1} cars", type.title, type.Cars.Count) }; if (item.Value == selected) item.Selected = true; typesList.Add(item); } return typesList; } private List<SelectListItem> _CreatePriceSelect(DHXScheduler scheduler, string selected) { var priceRanges = new string[] { "50-80", "80-120", "120-150" }; var prices = new List<SelectListItem>(){ new SelectListItem(){Value = "", Text = "Any"} }; foreach (var pr in priceRanges) { var item = new SelectListItem() { Value = pr, Text = string.Format("${0}", pr) }; if (pr == selected) item.Selected = true; prices.Add(item); } return prices; } private List<SelectListItem> _CreateTimeSelect(DHXScheduler scheduler, string selected) { var opts = new List<SelectListItem>(); for (var i = scheduler.Config.first_hour; i < scheduler.Config.last_hour; i++) { var value = string.Format("{0}:00", i < 10 ? "0" + i.ToString() : i.ToString()); var item = new SelectListItem() { Text = value, Value = value }; if (value == selected) item.Selected = true; opts.Add(item); } return opts; } protected IQueryable<Car> _SelectCars(RentalDataContext context) { return _SelectCars(context, null); } private DateTime _ParseDate(string date, string time) { var datestr = string.Format("{0} {1}", date, time); DateTime result = new DateTime(); DateTime.TryParse(datestr, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out result); return result; } protected IQueryable<Car> _SelectCars(RentalDataContext context, FormState state) { IQueryable<Car> cars = from car in context.Cars select car; if (state == null) return cars; string _type = state.Type; string _price = state.Price; var _from = default(DateTime); var _to = default(DateTime); //try to parse time range if (!string.IsNullOrEmpty(state.DateFrom)) { _from = _ParseDate(state.DateFrom, state.TimeFrom); _to = _ParseDate(state.DateTo, state.TimeTo); if (_from.CompareTo(default(DateTime)) != 0 && _to.CompareTo(default(DateTime)) == 0)//only start date set { _to = _from.AddHours(1); } } //filter by car type if (!string.IsNullOrEmpty(_type)) { int type = context.Types.First().id; if (!string.IsNullOrEmpty(_type)) { int.TryParse(_type, out type); } cars = cars.Where(c => c.TypeId == type); } //filter by price if (!string.IsNullOrEmpty(_price)) { var price = _price.Split('-'); int low = int.Parse(price[0]); int top = int.Parse(price[1]); cars = cars.Where(c => c.Price <= top && c.Price >= low); } if (state.DateFilter && _from != default(DateTime) && _to != default(DateTime)) { //select cars, which are available in specified time range cars = from car in cars where car.Orders.Count == 0 || car.Orders.Where(o => o.end_date > _from && o.start_date < _to).Count() == 0 select car; } return cars; } protected void _ConfigureViews(DHXScheduler scheduler, IEnumerable cars) { var units = new TimelineView("Orders", "car_id"); units.X_Step = 2; units.X_Length = 12; units.X_Size = 12; //width of the first column units.Dx = 149; //row height units.Dy = 76; //order bar height units.EventDy = units.Dy - 5; units.AddOptions(cars); units.RenderMode = TimelineView.RenderModes.Bar; scheduler.Views.Clear(); scheduler.Views.Add(units); scheduler.InitialView = scheduler.Views[0].Name; } protected void _ConfigureLightbox(DHXScheduler scheduler, IEnumerable cars) { scheduler.Lightbox.Add(new LightboxText("text", "Contact details") { Height = 42, Focus = true }); scheduler.Lightbox.Add(new LightboxText("description", "Note") { Height = 63 }); var select = new LightboxSelect("car_id", "Car Brand"); scheduler.Lightbox.Add(select); scheduler.Lightbox.Add(new LightboxText("pick_location", "Pick up location") { Height = 21 }); scheduler.Lightbox.Add(new LightboxText("drop_location", "Drop off location") { Height = 21 }); select.AddOptions(cars); scheduler.Lightbox.Add(new LightboxTime("time", "Time period")); } public ContentResult Data() { return new SchedulerAjaxData((new RentalDataContext()).Orders); } public ContentResult Save(int? id, FormCollection actionValues) { var action = new DataAction(actionValues); RentalDataContext data = new RentalDataContext(); try { var changedEvent = (Order)DHXEventsHelper.Bind(typeof(Order), actionValues); switch (action.Type) { case DataActionTypes.Insert: data.Orders.InsertOnSubmit(changedEvent); break; case DataActionTypes.Delete: changedEvent = data.Orders.SingleOrDefault(ev => ev.id == action.SourceId); data.Orders.DeleteOnSubmit(changedEvent); break; default:// "update" var eventToUpdate = data.Orders.SingleOrDefault(ev => ev.id == action.SourceId); DHXEventsHelper.Update(eventToUpdate, changedEvent, new List<string>() { "id" }); break; } data.SubmitChanges(); action.TargetId = changedEvent.id; } catch { action.Type = DataActionTypes.Error; } return (new AjaxSaveResponse(action)); } } }

The calendar with a date filter is ready.

Part III. Signing rent boxes with rent duration period

Customize the template to show the rent duration period in the rent box (e.g. “Rented for 4 hours”). Add the required attributes to scripts.js:

//set displayed text var durations = { day: 24 * 60 * 60 * 1000, hour: 60 * 60 * 1000 }; var get_formatted_duration = function (start, end) { var diff = end - start; var days = Math.floor(diff / durations.day); diff -= days * durations.day; var hours = Math.floor(diff / durations.hour); diff -= hours * durations.hour; var results = []; if (days) results.push(days + " days"); if (hours) results.push(hours + " hours"); return results.join(", "); };

And finally, update Index.cshtml to display the duration in the rent box:

scheduler.templates.event_bar_text = function (start, end, event) { var text = "Rented"; return text + " for " + get_formatted_duration(start, end);

That’s it! Car rental application for ASP.NET MVC3 Razor is ready to use.

Sign up now and get a ready car rental service.

If you find this tutorial helpful, you are welcome to comment below and share it with your friends.