Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Getting It Right for Your Customers

2.00/5 (4 votes)
6 Aug 2018MIT4 min read 8.2K   40  
A custom date control to solve my customers needs and requirements
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:

  1. Display by years
    1. The selectable dates would be January 1, 2015 through December 31, 2017
    2. The formatted display dates would be 2015 through 2017
    3. Three possible selections
  2. Display by quarters
    1. The selectable dates would be January 1, 2015 through June 30, 2018
    2. The formatted display dates would be 2015/Q1 through 2018/Q2
    3. Fourteen possible selections
  3. Display by months
    1. The selectable dates would be January 1, 2015 through July 31, 2018
    2. The formatted display dates would be 1/1/2015 through 7/31/2018
    3. 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

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.

JavaScript
<script type="text/javascript">

    // Notice we can initialize for any year range, if nothing set, it uses current year
    // from current year - 3 years

    //var drs = new DateRanger(2012, 2020); Future dates will only validate to current year
    var drs = new DateRanger();
    //This is used to store the returned object from setCurrentDateView
    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:

JavaScript
function changeBasis() {
        cdv = drs.setCurrentDateView($('#ddlBasis').find
                   (":selected").val()); //control sets view
        changeslider();
    }

$(function () {

        cdv = drs.setCurrentDateView('Month'); //control sets view

        //initializing slider and setting it's slide function
        $("#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.

JavaScript
$("#startlbl").text(cdv.display(sdateIndex));
$("#endlbl").text(cdv.display(edateIndex));

//These can be hidden fields and returned on postback
//For demonstration purposes, they are shown on the page.

$("#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.

JavaScript
function appendYears(start_year, end_year) {
        //re-initialize
        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

JavaScript
function calcDateFrames() {
    //Fill in the valid_* arrays

    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).

JavaScript
    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"); };
                //the number is simply a guess as to how large of a spread 
                //you want to start with.
                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"); };
                //the number is simply a guess as to how large of a spread 
                //you want to start with.
                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"); };
                //the number is simply a guess as to how large of a spread 
                //you want to start with.
                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.

JavaScript
    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.

  1. TestSlider.html
    1. A simple html file which contains references to JavaScript and CSS files.
    2. Contains a div for the slider, drop down for the mode as well as several labels for the display of dates selected.
    3. JavaScript glue logic to attach the visual controls to the logic code.
  2. DateRanger.js
    1. Follows the Module Design pattern
    2. 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.
    3. 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.

License

This article, along with any associated source code and files, is licensed under The MIT License