Scheduler .NET

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DHTMLX.Scheduler;
namespace LoadMarkedTimespans.Controllers
{
 public class SchedulerAdminController : Controller
 {
 // GET: Scheduler
 public ActionResult Index()
 {
 var scheduler = new DHXScheduler(this);
 scheduler.Extensions.Add(SchedulerExtensions.Extension.ActiveLinks);
 scheduler.Skin = DHXScheduler.Skins.Flat;
 return View(scheduler);
 }
 }
}

Create a new view Views->SchedulerAdmin->Index

add view to calendar

It should have the following code:

@{
 ViewBag.Title = "Admin";
}
<div style="width:100%;height:800px">
 @Html.Raw(Model.Render())
</div>

And open the page. At this stage you probably see a couple of glitches in the calendar, wich are caused by over-general css rules of a project template:

calendar view

3.2 Styles

Open Content/Site.css and find the following style:

/* Set width on the form input elements since they're 100% wide by default */
input,
select,
textarea {
 max-width: 280px;
}

In order not to break the pages generated by default, we won’t remove this style, but make it slightly more specific so that it won’t conflict with the scheduler:

/* Set width on the form input elements since they're 100% wide by default */
form input,
form select,
form textarea {
 max-width: 280px;
}

Another thing you might want to add is a border at the left side of the scheduler. By default, it is not shown, but can be added via css. Add the following styles to the css file:

.dhx_cal_data{
 border-left: 1px solid #CECECE;
}
.dhx_cal_header{
 border-left: 1px solid transparent !important;
}

You can also move css setting for the scheduler container div there:

.calendar-container{
 width:100%;
 height:800px;
}

Since we have two different pages with calendars, one for regular users and the other for managers, let’s create another view right away.

3.3 User part

Create an empty controller ‘Scheduler’ and go to ‘Views’-> ‘Schedule’. Create ‘Index.chtml’. Copy the code from Views->SchedulerAdmin->Index.cshtml and change the title to “User”. Repeat it for the controller action. We could put a duplicated code of both actions to a separate method, however for now there is not so much of it to bother.

Your code will look like the following:

Views - Scheduler - Index:
@{
 ViewBag.Title = "User";
}
@section scripts{
 <script src="~/Scripts/scheduler-client.js"></script>
 @Html.Raw(Model.GenerateLinks())
 <script>
 @Html.Raw(Model.GenerateJSCode())
 </script>
}
<div class="calendar-container">
 @Html.Raw(Model.GenerateMarkup())
</div>

Controllers - SchedulerController:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DHTMLX.Scheduler;
namespace LoadMarkedTimespans.Controllers
{
 public class SchedulerController : Controller
 {
 // GET: Scheduler
 public ActionResult Index()
 {
 var scheduler = new DHXScheduler(this);
 scheduler.Extensions.Add(SchedulerExtensions.Extension.ActiveLinks);
 scheduler.Skin = DHXScheduler.Skins.Flat;
 return View(scheduler);
 }
 }
}

3.4 Login rules

Next, let’s configure login rules. Unauthorized users should be redirected to the login page. After login, users and admins should go to their calendar pages.

Since we use the Home controller as a default entry point, we can add Authorize attributes there in order to manage access:

 public class HomeController : Controller
 {
 [Authorize]
 public ActionResult Index()
 {
 if (User.IsInRole("Admin")) 
 {
 return RedirectToAction("Index", "SchedulerAdmin");
 }
 else
 {
 return RedirectToAction("Index", "Scheduler");
 }
 }

Then we need to add checks for authorization to calendar controllers.

Let’s restrict access of non-authorized users to both calendars. Additionally, it will require Admin role to access the admin calendar:

SchedulerController.cs:

namespace LoadMarkedTimespans.Controllers
{
 	[Authorize]
 	public class SchedulerController : Controller
 	{
public ActionResult Index()
{
 			 if (User.IsInRole("Admin"))
 			 this.RedirectToAction("Index", "SchedulerAdmin");
 			 ... 
 		}
	}
}

SchedulerAdminController.cs:

namespace LoadMarkedTimespans.Controllers
{
 [Authorize(Roles = "Admin")]
 public class SchedulerAdminController : Controller
 {
 ….
 }
}

Now we have a template for an application - configured login with a couple of users and pages with a calendar.

3.5 Define a model

Now we’ll implement basic functionality of the schedulers in order to give users an ability to create bookings and admin to view them.

Define a model class for calendar event. Create a file SchedulerEvent.cs and define there a class with the following properties:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using DHTMLX.Scheduler;
namespace LoadMarkedTimespans.Models
{
 public class SchedulerEvent
 {
 [Key]
 [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
 [DHXJson(Alias = "id")]
 public int ID { get; set; }
 [DHXJson(Alias = "text")]
 public string Text { get; set; }
 [DHXJson(Alias = "start_date")]
 public DateTime StartDate { get; set; }
 [DHXJson(Alias = "end_date")]
 public DateTime EndDate { get; set; }
 public string UserId { get; set; }
 }
}

We already have a database context class which was created with the Identity classes. We’ll attach our new model to this context. Firstly, we’ll move the context to a separate file. Copy ApplicationDbContext class form IdentityModels .cs, create a new file ApplicationDbContext.cs and paste the class there.

using System.Data.Entity;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
namespace LoadMarkedTimespans.Models
{
 public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
 {
 public ApplicationDbContext()
 : base("DefaultConnection", throwIfV1Schema: false)
 {
 }
 public static ApplicationDbContext Create()
 {
 return new ApplicationDbContext();
 }
 public System.Data.Entity.DbSet<LoadMarkedTimespans.Models.SchedulerEvent> SchedulerEvents { get; set; }
 }
}

3.6 CRUD operations

When it’s done, we’ll implement a CRUD logic for our calendars. Let’s start with the regular user, open SchedulerController.cs. Firstly, add a couple of namespaces, which will be needed later:

using DHTMLX.Scheduler;
using DHTMLX.Scheduler.Data;
using DHTMLX.Common;
using System.Data.Entity;
using LoadMarkedTimespans.Models;

The basic loading and saving of calendar data is quite simple. The code is similar to that described in the previous tutorial, so we’ll just paste it without a big explanation.

Load data:

public ContentResult Data()
{
 var context = new ApplicationDbContext();
 var apps = context.SchedulerEvents.ToList();
 return new SchedulerAjaxData(apps);
}

Operations of changing data:

public ActionResult Save(int? id, FormCollection actionValues)
{
 var action = new DataAction(actionValues);
 var context = new ApplicationDbContext();
 try
 {
 var changedEvent = DHXEventsHelper.Bind<SchedulerEvent>(actionValues);
 switch (action.Type)
 {
 case DataActionTypes.Insert:
 context.SchedulerEvents.Add(changedEvent);
 break;
 case DataActionTypes.Delete:
 context.Entry(changedEvent).State = EntityState.Deleted;
 break;
 default:// "update" 
 context.Entry(changedEvent).State = EntityState.Modified;
 break;
 }
 context.SaveChanges();
 action.TargetId = changedEvent.ID;
 }
 catch
 {
 action.Type = DataActionTypes.Error;
 }
 return (new AjaxSaveResponse(action));
}

And enable loading and saving data on the client-side:

public ActionResult Index()
{
 var scheduler = new DHXScheduler(this);
 scheduler.Extensions.Add(SchedulerExtensions.Extension.ActiveLinks);
 scheduler.Skin = DHXScheduler.Skins.Flat;
 scheduler.LoadData = true;
 scheduler.EnableDataprocessor = true;
 return View(scheduler);
}

With the default settings scheduler will use ‘Controller/Save’ and ‘Controller/Data’ routes, so the client-side operations will be binded to the actions we’ve just defined.

Let’s login as one of the users and checkout how the application works now:

ready calendar in asp net

So, we have a calendar that supports creating the events. All users can see and edit appointments of other users.

3.7 Hiding another user’s events

Let’s fix it. We’ll bind events to the users who have created them, and users will see only their events.

We’ll add simple checking for user id on selecting data, and will assign the current user id to the event that he has saved.

Add this namespace:

using Microsoft.AspNet.Identity;

Add following changes to the actions:

public ContentResult Data()
{
 var context = new ApplicationDbContext();
 var appUserId = User.Identity.GetUserId();
 var apps = context.SchedulerEvents.Where(e => e.UserId == appUserId).ToList();
 return new SchedulerAjaxData(apps);
}
public ActionResult Save(int? id, FormCollection actionValues)
{
 var action = new DataAction(actionValues);
 var context = new ApplicationDbContext();
 try
 {
 var changedEvent = DHXEventsHelper.Bind<SchedulerEvent>(actionValues);
 changedEvent.UserId = User.Identity.GetUserId();
 ….
 }
 catch
 {
 action.Type = DataActionTypes.Error;
 }
 return (new AjaxSaveResponse(action));
}

Now users have access only to their events.

3.8 Data format settings

As far as we load only the currently displayed events, the page will send the required date range to the server. In order to be sure that the dates will be parsed correctly regardless the server environment settings we’ll set the invariant culture for the application. Open web.config file and add this setting to <system.web> group.

<globalization culture="en-US" uiCulture="en-US" />

3.9 Blocking of time

The next step and mostly the goal of this tutorial - in user calendar we need to dynamically block the times that have been already booked by other clients and indicate it in the calendar.

Firstly, add the required scheduler extensions - one to highlight time areas in calendar and one to prevent creation of overlapping events. Open Index action of ‘SchedulerController’ and add the following two lines:

public class SchedulerController : Controller
{
 public ActionResult Index()
 {
 var scheduler = new DHXScheduler(this);
	scheduler.Extensions.Add(SchedulerExtensions.Extension.ActiveLinks);
 scheduler.Extensions.Add(SchedulerExtensions.Extension.Limit);
 scheduler.Extensions.Add(SchedulerExtensions.Extension.Collision);
 }
}

And enable dynamic loading mode of the scheduler. This will prevent scheduler from loading all appointments that are stored in database at once, and will load only those that should be currently displayed:

scheduler.EnableDynamicLoading(SchedulerDataLoader.DynamicalLoadingMode.Month);

Open ‘SchedulerController’ and update the Data action in order to load only requested timespans and load events of other users as blocked intervals:

public ContentResult Data(DateTime from, DateTime to)
{
 var context = new ApplicationDbContext();
 var appUserId = User.Identity.GetUserId();
 // load current user's events as appointments
 var apps = context.SchedulerEvents
 .Where(e => e.UserId == appUserId && e.StartDate < to && e.EndDate > from).ToList();
 // load others as blocked intervals
 var blocked = context.SchedulerEvents
 .Where(e => e.UserId != appUserId && e.StartDate < to && e.EndDate > from)
 .Select(e => new { e.StartDate, e.EndDate}).ToList();
 var response = new SchedulerAjaxData(apps);
 response.ServerList.Add("blocked_time", blocked);
 return response;
}

The next part should be done on the client via JS.

In the folder /Scripts create a JS file where we’ll put our custom code, name it scheduler-client.js

We’ll define a host object for that page and define there the methods we need. Here is a complete code, we’ll explain some parts of it below:

window.schedulerClient = {
 init: function () {
 scheduler.serverList("blocked_time");//initialize server list before scheduler initialization
 scheduler.attachEvent("onXLS", function () {
 scheduler.config.readonly = true;
 });
 scheduler.attachEvent("onXLE", function () {
 var blocked = scheduler.serverList("blocked_time");
 schedulerClient.updateTimespans(blocked);
 blocked.splice(0, blocked.length);
 //make scheduler editable again and redraw it to display loaded timespans
 scheduler.config.readonly = false;
 scheduler.setCurrentView();
 });
 },
 
 updateTimespans: function (timespans) {
 // preprocess loaded timespans and add them to the scheduler
 for (var i = 0; i < timespans.length; i++) {
 var span = timespans[i];
 
 span.start_date = scheduler.templates.xml_date(span.StartDate);
 span.end_date = scheduler.templates.xml_date(span.EndDate);
 // add a label
 span.html = scheduler.templates.event_date(span.start_date) +
 " - " +
 scheduler.templates.event_date(span.end_date);
 //define timespan as 'blocked'
 span.type = "dhx_time_block";
 scheduler.deleteMarkedTimespan(span);// prevent overlapping
 scheduler.addMarkedTimespan(span);
 }
 }
};

We’ve defined a global object that will hold all methods and variables, which we’ll define for this page. In ‘schedulerClient.init’ methods we’ll do all settings that has to be done on the client-side.

Each time the data is loaded, the response will contain a collection of blocked times that are provided as a named collection 'blocked_time'. We parse these items and add them to the scheduler as marked timespans.

When it’s done, we add a JS file to the page with the scheduler (Views - Scheduler - Index.cshtml)

@section scripts{
 <script src="~/Scripts/scheduler-client.js"></script>
 @Html.Raw(Model.GenerateLinks())
 <script>
 @Html.Raw(Model.GenerateJSCode())
 </script>
}
<div class="calendar-container">
 @Html.Raw(Model.GenerateMarkup())
</div>

Actually, we did something more here. We put code in a ‘scripts’ section, that will be rendered at the bottom of the page, in order to speed up the response time of the page. We’ll follow that approach and put all js/css related to dhtmlxScheduler there as well and configure server side to run our code right before the scheduler is initialized.

Controllers-SchedulerController.cs:

public class SchedulerController : Controller
{
 // GET: Scheduler
 [Authorize]
 public ActionResult Index()
 {
		….
// run the init
scheduler.BeforeInit.Add("schedulerClient.init()");
….
 }
}

Open /Content/Site.css and add the following style for the blocked times:

.dhx_time_block{
 text-align: center;
 font-size: large;
}

Let’s try it out!

We’ve logged in as one of the users and created a couple of events:

events in calendar

Next, let logout and select another user from the list.

calendar events

Now we'll make final adjustments to the Admin page. Admin should have full control over appointments, including reassigning them between users.

Let’s add user selection to the details form and display the related user name right next to the event description:

using DHTMLX.Scheduler.Controls;
using LoadMarkedTimespans.Models;
namespace LoadMarkedTimespans.Controllers
{
 [Authorize(Roles = "Admin")]
 public class SchedulerAdminController : Controller
 {
 // GET: SchedulerAdmin
 public ActionResult Index()
 {
 var scheduler = new DHXScheduler(this);
 
 ...
 scheduler.Lightbox.Add(new LightboxText("text", "Description"));
 var context = new ApplicationDbContext();
 var users = context.Users.Select(u => new { key = u.Id, label = u.Email }).ToList();
 var select = new LightboxSelect("UserId", "User");
 select.ServerList = "users";
 select.AddOptions(users);
 scheduler.Lightbox.Add(select);
 scheduler.Lightbox.Add(new LightboxTime("time", "Time period"));
 ...
 }

Note that we've specified the name of a users collection in this line:

select.ServerList = "users";

that will allow us to access this list of users on the client side.

Now redefine the labels of the appointments.The template will be redefined on the client side, so let’s add a js file the same way we did for the users’ calendar - create a file Scripts/scheduler-admin.js.
Again, we'll define a host object that will store all public methods we'll declare. This one will be simpler since we only need to redefine a template for event labels open /Scripts/scheduler-admin.js and add a template declaration:

window.schedulerAdmin = {
 init: function () {
 scheduler.templates.event_bar_text = scheduler.templates.event_text =
 function (start, end, ev) {
 var user = schedulerAdmin.findUser(ev.UserId);
 text = (user ? "<b>" + user.label + "</b> - " : "") + ev.text;
 return text;
 }
 },
 findUser: function (id) {
 var users = scheduler.serverList("users");
 for (var i = 0; i < users.length; i++) {
 if (users[i].key == id)
 return users[i];
 }
 }
};

When it’s done, we’ll add a JS file to the page with the scheduler (Views - SchedulerAdmin - Index.cshtml)

@{
 ViewBag.Title = "Admin";
}
@section scripts{
 <script src="~/Scripts/scheduler-admin.js"></script>
 @Html.Raw(Model.GenerateLinks())
 <script>
 @Html.Raw(Model.GenerateJSCode())
 </script>
}
<div class="calendar-container">
 @Html.Raw(Model.GenerateMarkup())
</div>

And finally, link the script to the scheduler - open Controllers/SchedulerAdminController.cs and add the following line to the Index action:

public class SchedulerAdminController : Controller
{
 public ActionResult Index()
 {
 var scheduler = new DHXScheduler(this);
 ...
 scheduler.BeforeInit.Add("schedulerAdmin.init();");
 
 return View(scheduler);
}

full calendar

You are welcome to subscribe to our Scheduler .NET related news and download a ready asp.net calendar control example with loading blocked timespans:

If you find this tutorial helpful or you have any questions thereupon, you are welcome to share your opinion below.

Author

Svetlana

Viktoria Langer

DHTMLX Scheduler .NET product care manager and customer manager since 2012. Interested in ASP.NET and trying to create valuable content.

Recent Blog Posts

AltStyle によって変換されたページ (->オリジナル) /