LBD: Adding Datetimepicker control to MVC project

Note: Learning By Doing (LBD) is an ongoing series of posts I am writing where I work through an everyday problem that I am having while coding. I try to document everything that I do, including all the things that don’t work. You can read more about why here.

The problem

I have a form in MVC5 that sets a date and time and I need a popup control to allow users to easily enter it.

I don’t want to have two controls, one for date and one for time.

The solution v1

As the project is using MVC5 it has Bootstrap built in, so the first thing to do was check what it could offer me. From a quick search it seems I could use either the Bootstrap-datepicker.js or jquery-ui version, they are both wired up the same way, I chose jquery:

Add the ‘datepicker’ class to the control

<div class="col-md-10">
                @Html.TextBoxFor(model => model.StartTime, new {@class = "form-control datepicker "})
                @Html.ValidationMessageFor(model => model.StartTime)
</div>

Note in the code above the control is @Html.TextBoxFor. When MVC scaffolds a view for a model normally it uses @Html.EditorFor. I had to change the type of control so that the CSS classes were applied to it properly, when it was @Html.EditorFor then the javascript we are about to add was not be able to find the correct control to work with.

Javascript to wire it up:

$('.datepicker').datepicker();

Finally add the correct include to the BundleConfig.cs file:

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js",
                        "~/Scripts/jquery-ui*"));

The result was not great:

I had a datepicker, but the formatting was off.

This, I realised, was because I hadn’t included the jquery css files. That could be fixed in the BundleConfig.cs file:

 bundles.Add(new StyleBundle("~/Content/css").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/themes/base/jquery.ui.all.css",
                      "~/Content/site.css"));

Which gave me what I wanted:

Except for one problem; there was no way to enter the time!

The Solution v2

After thinking about what I was trying to achieve and how best to get there, and a little searching, I decided to get rid of the jquery scripts . Whilst it appears to be possible to add a time picker component to the datepicker, it didn’t look all that easy. I also found a Bootstrap DateTimePicker component that seemed to do everything in one simple step. I didn’t want to mix other Bootstrap functions with jquery, I wanted to keep to one library if possible.

I had to just adjust the class on the control I wanted to use (technically this didn’t need to be done, but I wanted it to be as clear as possible):

<div class="col-md-10">
                @Html.TextBoxFor(model => model.StartTime, new {@class = "form-control datetimepicker "})
                @Html.ValidationMessageFor(model => model.StartTime)
</div>

Then update the javascript:

$('.datetimepicker').datetimepicker();

Add the two files that come in the zip, one css and one javascript to the project (I wasted some time here as I added them to the wrong project within the solution!) and then update the BundleConfig.js file to include them:

bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
                      "~/Scripts/bootstrap.js",
                      "~/Scripts/bootstrap-datetimepicker.min.js",
                      "~/Scripts/respond.js"));

bundles.Add(new StyleBundle("~/Content/css").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/bootstrap-datetimepicker.min.css",
                      "~/Content/site.css"));

and it didn’t work. No popup when I clicked in the control. Time to debug.

Turns out I hadn’t actually updated the javascript to setup the datetimepicker control. Once that was sorted we had this:

A picker stuck in the top corner and a bunch of javascript errors. More debugging required.

After some Googling and reading code I found the problem was that this version of the datetimepicker was built for Bootstrap v2, MVC5 upgrades Bootstrap to v3 which is what causes the errors I was seeing.

Back to square one!

The Solution v3

A bit more searching lead me to this. A different datetimepicker, and one that works with Bootstrap v3.

Even better, this one can be installed via Nuget.

Install-Package Bootstrap.v3.Datetimepicker

Running this command added new versions of bootstrap-datetimepicker.js and bootstrap-datetimepicker.css to my project. The only additional configuration I needed to do was add moment.js to the BundleConfig.cs file:

bundles.Add(new ScriptBundle("~/bundles/moment").Include(
                        "~/Scripts/moment.js"));

Ensure this new bundle is added before the bootstrap bundle as bootstrap requires moment to be loaded before it does.

NOTE: I didn’t have to change the HTML or javascript in the view as it was still setup for the previous controls I tried and this one works in exactly the same way.

This gave me (drumroll please……):

With working time section:

As I am going to need to add this control to both the creation and editing pages the simplest thing to do was to pull the javascript out to its own file. This means all of the default values I want are in a single place that I only have to update once for all of the datetimepickers to change in the app:

$('.datetimepicker').datetimepicker({
    pickDate: true,                 //en/disables the date picker
    pickTime: true,                 //en/disables the time picker
    useMinutes: false,              //en/disables the minutes picker
    useSeconds: false,              //en/disables the seconds picker
    startDate: moment().subtract('days', 1)// a minimum date
});

The startDate option allows me to set the control so only dates in the future can be selected. To set this I use the substract() method from moment.js. The control will only then allow the setting of the current day or any day in the future which is perfect for my needs.

I then just need to add the newly created file (datetimepicker-setup.js) to the Bootstrap bundle in BundleConfig.js:

bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
                      "~/Scripts/bootstrap.js",
                      "~/Scripts/bootstrap-datetimepicker.js",
                      "~/Scripts/datetimepicker-setup.js",
                      "~/Scripts/respond.js"));

Finished!

Conclusion

The whole process took me about 90 minutes, but at least half of that was because of picking the version of the control that was incompatible. But then that is the point of posts like this, putting down what worked and when it didn’t why not, and how did I fix it.

If I had found the correct Bootstrap version of the control with my first search and installed it with Nuget it would have been done in about 10 minutes.

What I think I can take from this whole experience is that I need to start searching Nuget first when I am looking for a component or control as that is the safest and easiest place to get it from that will require the least amount of configuration.