Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Making an Event Calendar for Trucking/Shipping with C# MVC and jQuery.Fullcalendar

0.00/5 (No votes)
10 Feb 2018 1  
A complete event calendar, using trucking/shipping as an example, built with Visual Studio Community 2015, C# MVC and jQuery.Fullcalendar

Our company grows forest seedlings for our clients and then delivers to them. The delivering process is called trucking or shipping, which is a very complicated process when we have huge quantities of seedlings for lots of clients. So I made an event calendar in 2015, using the free Microsoft Visual Studio Community 2015 and jQuery.Fullcalendar by Adam Shaw, for managing this process online, and for our clients to check their seedlings shipping status online. It is recently put to use. Now I'd like to share my experience and some of my codes, by building a new, simple event calendar from scratch, step by step, to a working calendar, using trucking or shipping as an example.

AJSON has an excellent article Full calendar – A complete web diary system for jQuery and C# MVC, on how to use the jQuery plugin. Except he made it too complicated and did not quite observe the MVC rule. I will explain these when I get there.

Here are the usual steps:

  • First, let's use Visual Studio Community 2015, create a new ASP.NET Web Application, and call it "TruckingCalendar". Choose MVC and click OK. A blank Trucking Calendar application is created.

  • We need a database to hold the trucking events. So the free Microsoft SQL Server 2014 Express is used to create the database, called "Trucking".
  • Create a table in the database, and call it "Trucking" too. Add columns: TruckID (int, Primary Key, Identity Specification = Yes), TruckDate (smalldatetime), LoadMinutes (smallint), TruckStatus (tinyint). Not allow nulls. Default LoadMinutes to 60 (or any number) and TruckStatus to 0.

  • In the project, right click Models folder -> Add New Item -> Data -> ADO.NET Entity Data Model, and call it "TruckingModel". Click Add.
  • Then choose "EF Designer from database". Click Next. Then in Server name: type "./SQLEXPRESS", and in Database name: select the "Trucking" database that we just created. And Test Connection to show connected.
  • Click Next. It will create "TruckingEntities". Click Next to Choose Your Database Objects and Settings: Choose the Trucking table, and keep the default settings.
  • Click Finish and we have got a TruckingModel.edmx.

  • Now we need the jQuery.Fullcalendar package. Click Project -> Manage Nuget Packages..., and update all your packages already there.
  • Then browse and install jQuery.Fullcalendar, and Moment.js, since Fullcalendar uses Moment.js for DateTime settings.
  • Open BundleConfig.cs in App_Start folder. In the last bundles (the CSS bundle), add "~/Content/themes/base/all.css". Check your folder, see if the file is there. These CSS themes will be used by the Calendar.
  • Now we get to do the fun staff: the EF scaffolding. But do a project rebuild first. Then right click Controllers folder -> Add -> New scaffolded item..., select "MVC 5 Controller with views, using Entity Framework". Click Add.
  • In the Model class:, select Trucking, and in the data context class:, select TruckingEntities, keep the defaults. And click Add.

That's the whole setup, folks. Phew! It did take a lot of work, even for the easy part. Now is the hard part.

Now that the Trucking Controller is open, we are going to replace this:

// GET: Truckings
public ActionResult Index()
{
    return View(db.Truckings.ToList());
}

with this:

// GET: Calendar
public ActionResult Calendar(DateTime? truckDate)
{
    ViewBag.TruckDate = truckDate ?? DateTime.Now;
    return View();
}

ViewBag.TruckDate is used to get the nullable truckDate. If it is null, "Now" will be used. The purpose will be explained later.

Now we have to rename Index.cshtml in Truckings folder to Calendar.cshtml, according to the Controller change. Inside the View, change ViewBag.Title to "Trucking Calendar", and replace everything below it with the following 4 blocks of code (because the code is too long for 1 block):

<link rel='stylesheet' href='~/Content/fullcalendar.css' />
<script src='~/Scripts/moment.js'></script>
<script src='~/Scripts/fullcalendar.js'></script>

We can see that above is where the installed packages (Fullcalendar and Moments) get used. Codes continue...

<script>
$(document).ready(function () {
    $('#calendar').fullCalendar({
        header: {
            right: 'prev,next today',
            center: 'title',
            left: 'month,agendaWeek,agendaDay'
        },
        theme: true,
        timezone: 'local',
        editable: true,
        defaultDate: '@ViewBag.TruckDate',
        allDaySlot: false,
        selectable: true,
        slotMinutes: 15,
        events: '@Url.Action("GetEvents")',

The above is setup and automatic actions. Codes continue...

        eventClick: function(calEvent) {
            window.location='/Truckings/Details/' + calEvent.id;
        },

        dayClick: function (date, jsEvent, view) {
            if(view.name != 'month') {
                if (date > moment()) {
                    if (confirm("Add a new trucking?")) {
                        window.location = '@Url.Action("Create")' + '?truckDate=' + date.format();
                    }
                }
            }
            else {
                $('#calendar').fullCalendar('changeView', 'agendaDay');
                $('#calendar').fullCalendar('gotoDate', date);
            }
        },

        eventDrop: function(event, delta, revertFunc) {
            if (confirm("Confirm move?")) {
                UpdateEvent(event.id, event.start, event.end);
            }
            else {
                revertFunc();
            }
        },

        eventResize: function (event, delta, revertFunc) {
            if (confirm("Confirm change loading time?")) {
                UpdateEvent(event.id, event.start, event.end);
            }
            else {
                revertFunc();
            }
        }
    });
});

The above are human interactions. Codes continue...

function UpdateEvent(id, start, end) {
    var obj = { id: id, start: start, end: end }
    $.ajax({
        type: 'POST',
        url: '@Url.Action("UpdateEvent")',
        dataType: "json",
        contentType: "application/json",
        data: JSON.stringify(obj)
    });
}
</script>

After the script section, add: <div id='calendar'></div> to hold the Calendar. And that is the end of the code in Calendar.cshtml.

To link to the Calendar, open the Index.cshtml in Home folder, keep the "Home Page" title, but replace everything else with this action link:

<div class="jumbotron h3 text-center">
    @Html.ActionLink("Trucking Calendar", "Calendar", "Truckings", null, null)
</div>

Now run the project, click the Trucking Calendar link on the Home page, we get a blank Calendar.

Yay! If you get this far, congratulate yourself!

Now we need to use the database to input and output events.

First, let's make a ViewModel. Right click Models folder, add a Class, and call it TruckingVMs.cs. When it is open, replace the public class TruckingVMs with this code:

public class Events
{
    public int id;
    public string title;
    public DateTime start;
    public DateTime end;
    public string color;
}

We also need an Enum for TruckStatus, remembering its data type is tinyint. So right click Models folder, add a Class, and call it Enums.cs. When it is open, replace the generated code with this code:

using System.ComponentModel.DataAnnotations;
namespace TruckingCalendar.Models
{
    public enum TruckStatus : byte
    {
        [Display(Name = "Planned")]
        orange,
        [Display(Name = "Confirmed")]
        green,
        [Display(Name = "Changed")]
        red,
        [Display(Name = "Loaded")]
        darkcyan
    }
}

So the TruckStatus will have 4 status with 4 different colors, but stay in database as 0, 1, 2, 3.

Then click the model EvergreenModel.edmx, and the database diagram will show. Right click TruckStatus in the Trucking table -> Convert to Enum. Next, in Enum Type Name:, enter TruckStatus. Click Reference external type, and enter TruckingModel.TruckStatus. Click OK to convert. Now check the Trucking.cs partial class, it will show: public TruckingModel.TruckStatus TruckStatus { get; set; }. Somehow, the "TruckingModel." is not allowed here, although necessary in the edmx. It is a glitch in Visual Studio 2015. So we have to delete the "TruckingModel." from Trucking.cs.

Now we also need a display template and an editor template to display and edit the enum. Shahriar Hossain has an excellent article on how to make them: Dealing with Enum in MVC 5.1. So make those 2 templates like he shows.

Also add a DateTime.cshtml in the EditorTemplates folder, and replace the generated code with this:

@model Nullable
@{
    DateTime dt = DateTime.Now;
    if (Model != null)
    {
        dt = (System.DateTime)Model;
    }
    @Html.TextBox("", String.Format("{0:d}", dt.ToShortDateString() + ' ' +
    dt.ToShortTimeString()), new { @class = "form-control datecontrol", type = "datetime" })
}

This will set the DateTime to local time with a Time Zone (with the timezone: 'local' directive in the Calendar.cshtml), when we add a new event.

Eventually, we are ready to do some input and output. First, input. Open TruckingsController, go to public ActionResult Create(), change the generated code to:

public ActionResult Create(DateTime truckDate)
    {
        ViewBag.TruckDate = truckDate;
        return View();
    }

The truckDate here comes from the following code of the Calendar.cshtml, as shown before:

dayClick: function (date, jsEvent, view) {
            if(view.name != 'month') {
                if (date > moment()) {
                    if (confirm("Add a new trucking?")) {
                        window.location = '@Url.Action("Create")' + '?truckDate=' + date.format();
                    }
                }
            }
            else {
                $('#calendar').fullCalendar('changeView', 'agendaDay');
                $('#calendar').fullCalendar('gotoDate', date);
            }
        }

When you click a future time in the Calendar, the Calendar will ask you if you want to add a new trucking event. If you click OK, the Calendar will send you to the Controller's Create action, with the truckDate (DateTime) you just clicked. So the ViewBag.TruckDate has a selected value now, and we want to add the selected value to the Create.cshtml:

@Html.EditorFor(model => model.TruckDate,
new { htmlAttributes = new { @class = "form-control", @Value = ViewBag.TruckDate } })

Then go back to TruckingController's POST: Truckings/Create section, change:

return RedirectToAction("Index");

to:

return RedirectToAction("Calendar", new { truckDate = trucking.TruckDate });

This truckDate is redirected to Calendar action, and sent to Calendar View as defaultDate: '@ViewBag.TruckDate', so that the Calendar goes back to the month it come from. Many developers asked how to go back to the Calendar month where they left, this is the way to do it.

Do the same for the RedirectToAction in Edit and Delete section.

Then, go to the Trucking Views, at the end of each View, change:

@Html.ActionLink("Back to List", "Index")

to:

@Html.ActionLink("Back to Calendar", "Calendar", new { truckDate = Model.TruckDate })

Except for Create.cshtml, we have to use "ViewBag.TruckDate", which we already used for its selected value. So that they can all go back to where they come from when "Back to Calendar" is clicked.

OK. This is the input. What about the output? Ah..., just a moment, let me take a break...

Ok. Here we go again. First, let's look at the Calendar View: events: '@Url.Action("GetEvents")', which means, when the Calendar opens, it will go to the TruckingsController's GetEvents action to get events.

So we have to open the TruckingsController and add the GetEvents action at the end:

public JsonResult GetEvents(DateTime start, DateTime end)
{
    var truckings = (from t in db.Truckings
                    where t.TruckDate >= start &&
                    DbFunctions.AddMinutes(t.TruckDate, t.LoadMinutes) <= end
                    select new { t.TruckID, t.TruckDate, t.LoadMinutes, t.TruckStatus }).ToList()
    .Select(x => new Events()
    {
        id = x.TruckID,
        title = "Trucking " + x.TruckID + ", " + x.LoadMinutes + "min ",
        start = x.TruckDate,
        end = x.TruckDate.AddMinutes(x.LoadMinutes),
        color = Enum.GetName(typeof(TruckStatus), x.TruckStatus)
    }).ToArray();
    return Json(truckings, JsonRequestBehavior.AllowGet);
}

The start and end come from the Calendar, depending on it is a month, week or day, it will get events in that time period. The events then are sent to the ViewModel in this step: x => new Events(), and from the ViewModel to the Calendar, all in one action, and one place. And getting color is just one line of code. Boom! It is there.

In AJSON's project, he puts some controller action in his ViewModel, so that the controller has to call the ViewModel to do the calculation, then come back with the data, and the data has to transform several times before send to the Calendar. Not very efficient. Also, his way of getting the color is complicated and difficult.

Now, let's look more at the Calendar View:

eventClick: function(calEvent) {
            window.location='/Truckings/Details/' + calEvent.id;
        },

It means, if we click a trucking event, we will see the details of the event. From there, we can come back, or do some editing and come back. Nice.

We can also do some drag and drop of the events on the Calendar, as indicated by eventDrop: and eventResize:, and they all call for UpdateEvent function:

function UpdateEvent(id, start, end) {
    var obj = { id: id, start: start, end: end }
    $.ajax({
        type: 'POST',
        url: '@Url.Action("UpdateEvent")',
        dataType: "json",
        contentType: "application/json",
        data: JSON.stringify(obj)
    });
}

And the UpdateEvent in turn calls for the TruckingsController's UpdateEvent action. So we need to add that action to the end of the Controller:

public void UpdateEvent(int id, DateTime start, DateTime end)
{
    Trucking trucking = db.Truckings.Find(id);
    trucking.TruckDate = start;
    trucking.LoadMinutes = (short)(end - start).TotalMinutes;
    db.SaveChanges();
}

That's it folks! We are done! Run your project, click the link to the calendar. Click a future time and add some events; change status to see the color change; drag and drop the events to different time, date, week or month; click an event to see details; do some editing and deleting; click Back to Calendar to go back to original month. Here is my screen shot:

You should see something like this too.

Now it's time to celebrate!! Oh, wait..., you got questions? Suggestions? Thoughts? Check my GitHub repository: A complete event calendar, using trucking/shipping as an example.

References

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here