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.
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.
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
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:
Let's make the TypeId field the foreign key that refers to the [Type].id field.
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:
@Html.Raw(Model.Scheduler.GenerateCSS())
@Html.Raw(Model.Scheduler.GenerateJS())
@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;
}
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:
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:
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; }
}
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.
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.