In this post, you will see how the end user can select proper dates which are constrained by the defined rules.
Introduction
As a consultant, I am often asked what code I am most proud of. Over the years, I’ve learned that what I am most proud of is not necessarily how elegant my code is, but rather how elegant my final solution is for the customer.
Recently, I was working on a couple of web pages which did not meet their intended purpose. The purpose of the page was to match the time frames of an external resource. That resource would process and report files based on complete months, years and quarters.
I quickly determined the current solution of having two date controls (start date and end date) to manage the ranges was not working for them. It allowed the entry of values which would not match the time frames of the external resource. In fact, the pages were deemed so ineffective that they were not generally usable.
My solution was to create a date selector based off of a slider control from jQuery and JavaScript. Now the end user can select proper dates which are constrained by the rules defined above and the pages have now become usable. Most importantly, the customer was pleased with the final product.
Background
In all cases, the slider can slide to the first day of the selected range and the last day of the range selected.
Given the date August 3, 2018 and the year range set from 2015 through 2018, the following would be selectable by the control.
Depending on mode, the granularity of the control changes.
The control has three modes:
- Display by years
- The selectable dates would be January 1, 2015 through December 31, 2017
- The formatted display dates would be 2015 through 2017
- Three possible selections
- Display by quarters
- The selectable dates would be January 1, 2015 through June 30, 2018
- The formatted display dates would be 2015/Q1 through 2018/Q2
- Fourteen possible selections
- Display by months
- The selectable dates would be January 1, 2015 through July 31, 2018
- The formatted display dates would be 1/1/2015 through 7/31/2018
- Forty-three possible selections.
Here is a sample image of what the control will display for an end user.
Using the Code
To use the DateSlider
object, include the js file into the HTML file, ensuring that you include the jQuery and moment.js.
HTML
<link href="Content/themes/base/jquery-ui.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="Scripts/moment.js"></script>
<script src="Scripts/DateRanger.js"></script>
After these, you will need to add some 'Glue code' to connect the DateSlider
object to the controls on the page.
To start, create a script
section and add the following to initialize the control.
<script type="text/javascript">
var drs = new DateRanger();
var cdv = {};
Next, you will want to call the method setCurrentDateView
with the values ('month
','quarter
' or 'year
') on the page loaded event and the change view dropdown:
function changeBasis() {
cdv = drs.setCurrentDateView($('#ddlBasis').find
(":selected").val());
changeslider();
}
$(function () {
cdv = drs.setCurrentDateView('Month');
$("#dateSlider").slider({
range: true,
max: cdv.eIndex,
slide: function (event, ui) { changeslider(event, ui) }
});
changeslider();
})
The function changeslider
is really the glue that pulls it all together. It uses the DateRanger
methods (display and fullStartDate
) to retrieve the values used to update the page when the handles are changed.
$("#startlbl").text(cdv.display(sdateIndex));
$("#endlbl").text(cdv.display(edateIndex));
$("#startlblday").text(cdv.fullStartDate(sdateIndex))
$("#endlblday").text(cdv.fullEndDate(edateIndex));
Notice that the variable cdv
which is returned from the setCurrentDateView
method is the object which contains the functions to display and return appropriate indexes for the view selected.
DateRanger.cs
The DateRanger
object works in several phases.
Phase One
As the object is created, an array of datapoints is created and the array is validated for validity.
function appendYears(start_year, end_year) {
datapointCollection = [];
valid_years = [];
valid_quarters = [];
valid_months = [];
while (start_year <= end_year) {
datapointCollection.push(new initializeYear(start_year));
start_year++;
}
validateCurrent(datapointCollection);
}
Phase Two
After Phase 1 is complete, an array for each time frame is created.
valid_year
, valid_quarters
and valid_months
function calcDateFrames() {
for (var y = 0; y < datapointCollection.length; y++) {
if (datapointCollection[y].isValid)
valid_years.push({
StartMonth: 1,
EndMonth: 12,
year: datapointCollection[y].year });
for (var q = 0; q < datapointCollection[y].quarters.length; q++) {
if (datapointCollection[y].quarters[q].isValid)
valid_quarters.push({
StartMonth: q * 3 + 1,
EndMonth: q * 3 + 3,
year: datapointCollection[y].year,
name: datapointCollection[y].quarters[q].name })
for (var m = 0; m < datapointCollection[y].quarters[q].months.length; m++) {
if (datapointCollection[y].quarters[q].months[m].isValid)
valid_months.push({
StartMonth: q * 3 + m + 1,
EndMonth: q * 3 + m + 1,
year: datapointCollection[y].year,
name: datapointCollection[y].quarters[q].months[m].name })
}
}
}
}
Phase three
An object is returned which can be used by the changeslider
event to report on the internal arrays for the selected date view (month
, quarter
, year
).
DateRanger.prototype.setCurrentDateView = function (strDateView) {
var fdisplay = {};
var startIndex = 0;
var endIndex = 0;
var selectedStartDate = {};
var selectedEndDate = {};
switch (strDateView) {
case 'Month':
fdisplay = function (index)
{ return valid_months[index].name + '/' + valid_months[index].year; };
selectedStartDate = function (index)
{ return moment().year(valid_months[index].year).month
(valid_months[index].StartMonth - 1).startOf('month').format("M/D/YYYY"); };
selectedEndDate = function (index) { return moment().year
(valid_months[index].year).month(valid_months[index].EndMonth - 1).endOf
('month').format("M/D/YYYY"); };
startIndex = valid_months.length - 7;
endIndex = valid_months.length - 1;
break;
case 'Quarter':
fdisplay = function (index) { return valid_quarters[index].year +
'/' + valid_quarters[index].name; };
selectedStartDate = function (index)
{ return moment().year(valid_quarters[index].year).month
(valid_quarters[index].StartMonth - 1).startOf('month').format("M/D/YYYY"); };
selectedEndDate = function (index) { return moment().year
(valid_quarters[index].year).month(valid_quarters[index].EndMonth - 1).endOf
('month').format("M/D/YYYY"); };
startIndex = valid_quarters.length - 3;
endIndex = valid_quarters.length - 1;
break;
default:
fdisplay = function (index) { return valid_years[index].year; };
selectedStartDate = function (index) { return moment().year
(valid_years[index].year).month(valid_years[index].StartMonth - 1).startOf
('month').format("M/D/YYYY"); };
selectedEndDate = function (index)
{ return moment().year(valid_years[index].year).month
(valid_years[index].EndMonth - 1).endOf('month').format("M/D/YYYY"); };
startIndex = valid_years.length - 2;
endIndex = valid_years.length - 1;
break;
}
Finally, the object returns the functions selected in setCurrentDateView
back to the 'Glue code' in the html file.
return {
sIndex: startIndex,
eIndex: endIndex,
display: fdisplay,
fullStartDate: selectedStartDate,
fullEndDate: selectedEndDate
};
};
return DateRanger;
Points of Interest
The code requires a reference to moment.js which is used to determine the last day of each selection.
I've included two files which can be added to any existing project to test.
- TestSlider.html
- A simple html file which contains references to JavaScript and CSS files.
- Contains a
div
for the slider, drop down for the mode as well as several labels for the display of dates selected. - JavaScript glue logic to attach the visual controls to the logic code.
- DateRanger.js
- Follows the Module Design pattern
- It is an Immediately-Invoked-Function-Expressions (IIFE) encapsulating local variables and functions and returning a number of items used by the glue logic to display the controls correctly.
- Contains the logic to implements the rules defined previously.
Code
The code is supplied as part of this article, but additionally will be available on Github for latest and updates.
The code is available at:
In Conclusion
Business may know what they need to be successful but may not know how to get there or have the vocabulary to express their desires. When that occurs, it is up to us as developers to add that extra value which will help businesses meet and exceed their customers expectations.
Hope this helps. Feel free to use in your projects as well.