Download Free Demos

Car Rental Application in ASP.NET MVC3 Razor

This tutorial will let you learn how to modify DHTMLX Scheduler .NET web control in order to implement a car rental application with a search field and a customer's order form. You can see the demo of the ready application.

By following precise instructions, you will create a car rental sample for ASP.NET MVC3 in the shortest possible time. The tutorial covers essential steps of the new app creation and includes descriptions, pictures and full code examples.

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

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

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

Part 1. 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 available cars by price and type.

Initialization

As the first step, we will 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.

Database and Model Creation

Database

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 the foreign key that refers to the [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:

Let's make the Car_id field the foreign key that refers to the [Car].id field.

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

Model

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" tables 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 an Index.cshtml file. Delete the views we do not require from the folders "Home" and "Shared" - Error.cshtml and About.cshtml.

Right-click on the "Models" to create a ViewModel.cs file 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 on how we have implemented Scheduler .NET rendering and initialization as well as customized the calendar template:

1 . Scheduler .NET rendering

This time we have rendered Scheduler in an unusual way, on order to override some default CSS styles after loading and before Scheduler initialization. Instead of using Scheduler.Render(), we have done it in the following way:

  • 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

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 overrode 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";
};

Adding CSS classes

Then we proceeded 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;
}

Controller

The next step 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 a look at the most essential code snippets:

1 . Adding Collision extension

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);

2 . Implementing filtration

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;
}

3 . Setting Configuration

We set a 2-step time interval for car order with the scale of 12 cells in the Timeline View. 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;

4 . Adding select options

Create a list of select options in the controller and pass them to the 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);
    }

5 . Customizing customer form

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 in the picture below:

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

6 . Adding car images

Copy the available car images (image size is 80x48 px) to the Content folder of your project.

7 . Configuring database

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 images 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 2. Creation of Date and Time Filter

In the second part of the tutorial we will 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 will 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 the Scripts folder and create a file "scripts.js". Add a minical function for automatic display/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:

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; }
}

Controller and View

Date is selected from the two inputs: the date picker and the "select time" drop-down list.

We will 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's 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 into the _SelectCars method:

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 3. Showing rent boxes with 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(", ");
};

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 for use.

Sign up now and get a ready car rental service.

Was this article helpful?

Yes No