Tags

, , ,

A was working recently with a SharePoint custom form with contains a lot of textbox controls to server as date pickers. Of course, as many other developers, I choose jQuery date picker. So far, so good, but one the requests was to put a series of 4 or 5 controls in hierarchical structure. In other words, date selection of one control was not supposed to be less than previous control selection.

Documentation of jQuery date picker range selection is limited to 2 controls. So to bypass this, I created an Ajax non visual control to integrate and extend this functionality to more date pickers. I will present the code below, which is not very complicated for someone who works with Microsoft Ajax controls.

/// <Reference Name="MicrosoftAjax.js" />

Type.registerNamespace('Sys');


Sys.HierarchicDatePickers = function () {
    /// <summary>Create an instance of Sys.HierarchicDatePickers class</summary>
    Sys.HierarchicDatePickers.initializeBase(this, []);


    this._id;
    this._datepickersIds = [];
    this._pickersOptions = {};
    this._dateChanged = null;
}


Sys.HierarchicDatePickers.prototype = {

    initialize: function () {

        var instance = this;

        // Overwrite format date option if specified (will be improve in the next version)
        instance._pickersOptions['dateFormat'] = 'yy-mm-dd';


        for (var i = 0; i < this._datepickersIds.length; i++) {

            var ctl = $get(this._datepickersIds[i]);
            var jCtl = jQuery('#' + this._datepickersIds[i]);

            // If date picker is initialized, change date format option to our standard
            if (Sys.UI.DomElement.containsCssClass(ctl, 'hasDatepicker') === true) {
                jCtl.datepicker('option', 'formatDate', 'yy-mm-dd');
            }

            // If date picker is not initialized, create date picker
            if (Sys.UI.DomElement.containsCssClass(ctl, 'hasDatepicker') === false) {
                jCtl.datepicker(instance._pickersOptions);
            }


            // Trigger change event when date picker is closed, we will keep and execute also first previous on close handler
            var prevOnClose = jCtl.datepicker('option', 'onClose');
            jCtl.datepicker('option', 'onClose', function (dt, inst) {
                if (prevOnClose !== null) prevOnClose(dt, inst);
                instance._triggerChange(ctl);
            });


            this._add_changeHandler(i);
        }

        this._setPickersMinDate();

    },

    dispose: function () {
        this._dateChanged = null;
        this._pickersOptions = null;
        Sys.HierarchicDatePickers.callBaseMethod(this, 'dispose');
    },


    _add_changeHandler: function (index) {
        /// <summary>Add change handler for control (internal method)</summary>
        /// <param name="index"></param>
        var instance = this;

        $addHandler($get(this._datepickersIds[index]), 'change', function (evt) {
            instance._setPickersMinDate();
            if (instance._dateChanged !== null) instance._dateChanged.apply(instance);
        });
    },

    _triggerChange: function (element) {
        /// <summary>Trigger change event</summary>


        if ("createEvent" in document) {
            var evt = document.createEvent("HTMLEvents");
            evt.initEvent("change", false, true);
            element.dispatchEvent(evt);
        }
        else {
            $get(element).fireEvent('onchange');
        }
    },

    _setPickersMinDate: function () {
        /// <summary>Set picker min date (internal method)</summary>

        var minDate = $get(this._datepickersIds[0]).value.toString().trim();
        for (var i = 1; i < this._datepickersIds.length; i++) {
            var prev = $get(this._datepickersIds[i - 1]).value.toString().trim();
            minDate = (prev === '') ? minDate : prev;
            jQuery('#' + this._datepickersIds[i]).datepicker('option', 'minDate', minDate);
        }
    },


    set_id: function (id) {
        /// <summary>Set id for current instance of Sys.HierarchicDatePickers class</summary>
        /// <param name="id" type="String" mayBeNull="false" optiona='false">Id</param>
        /// <returns type="Sys.HierarchicDatePickers" />
        var e = Function.validateParameters(arguments, [{ name: 'id', type: String, mayBeNull: false, optional: false }], true);
        if (e) throw e;

        this._id = id;
    },

    get_id: function () {
        /// <summary>Get id for current instance of Sys.HierarchicDatePickers class</summary>
        /// <returns type="String" />

        return this._id;
    },

    set_pickersOptions: function (options) {
        /// <summary>Set date pickers options</summary>
        /// <param name="options" type="Object" mayBeNull="false" optiona='false">Pickers options</param>
        /// <returns type="Sys.HierarchicDatePickers" />
        var e = Function.validateParameters(arguments, [{ name: 'options', type: Object, mayBeNull: false, optional: false }], true);
        if (e) throw e;

        this._pickersOptions = options;
        return this;
    },

    get_pickersOptions: function (options) {
        /// <summary>Get date pickers options</summary>
        return this._pickersOptions;
    },

    set_datepickersIds: function (ids) {
        /// <summary>Set date pickers controls id</summary>
        /// <param name="ids" type="Array" elementType="String" elementMayBeNull="false">Controls id</param>
        /// <returns type="Sys.HierarchicDatePickers" />

        var instance = this;
        for (var i = 0; i < ids.length; i++) {
            var id = ids[i];
            if ($get(id) !== null && $get(id).tagName === 'INPUT' && $get(id).type === 'text') {
                instance._datepickersIds.push(id);
            }
        }
        return instance;
    },

    get_datepickersIds: function () {
        /// <summary>Get date pickers id</summary>
        /// <returns type="Array" elementType="String" />

        var instance = this;
        return instance._datepickersIds;
    },

    add_dateChanged: function (handler) {
        /// <summary>Add handler for date changed event</summary>
        /// <param name="handler" type="Function" mayBeNull="false" optional="false">Handler function</param>
        /// <returns type="Sys.HierarchicDatePickers" />
        var e = Function.validateParameters(arguments, [{ name: 'handler', type: Function, mayBeNull: false, optional: false }], true);
        if (e) throw e;

        this._dateChanged = handler;
        return this;
    },

    remove_dateChanged: function () {
        /// <summary>Remove handler for date changed event</summary>
        this._dateChanged = null;
    }

};

Sys.HierarchicDatePickers.registerClass('Sys.HierarchicDatePickers', Sys.Component);

For the moment, there are some limitation I want to bypass in the future. One of them is I am using YYYY-MM-DD date format, because is easy to work with it as string. But with a little help of some contributors, this can be extended.

Now, let’s assume we have 4 textbox controls with following id properties: StartDate, MiddleDate, AfterMiddleDate, EndDate. The requests is, of course, to have always dates selection in ascending in order. To do this we need put them in an array in the order we want to be.

var datepickers = ['StartDate', 'MiddleDate', 'AfterMiddleDate', 'EndDate'];

We might also want to add options for jQuery date pickers initialization and add a handler for change event.

var datepickersOptions = {
  buttonImage: '/teams/dealsupport/images/calendar.gif',
  showOn: 'button',
  buttonText: 'Select Date'
}

var changedHandler = function() {
// Do something here
// You can reference to instance of Sys.HierarchicDatePickers with this keyword
}

And now, the only thing we need to do is to initialize the class using $create Ajax function, which make our object life cycle to be managed by Ajax framework.

$create(Sys.HierarchicDatePickers, { 'id': 'pickers', 'datepickersIds': datepickers, 'pickersOptions': datepickersOptions }, { 'dateChanged': 	changedHandler }, {}, null);

Is not the perfect solution, but you can improve it and extend it and make it better. In any case, I think is an easier solution than writing lines and lines of code each time you need to have this functionality. Inheriting Sys.Component class, the code is ready to be reusable.

Advertisements