Sunday 4 May 2014

Customizing Date Picker and Date Field in Ext JS

Ext JS provides a date field component for taking date input on forms.


This component only accepts a date value. What if there is a requirement to accept some other value, e.g. "Not Provided". You have to keep the field blank, as entering "Not Provided" results into validation error.


But keeping a field blank will make the allowBlank : false validation fail. How to allow the user to submit the form at the same time ensuring that s/he has provided a correct value for date field (including "Not Provided"). 

To resolve this challenge, we have customized the date picker and date field provided by Ext JS framework to include a feature to have "Not Provided" button and to consider "Not Provided" as a valid data. The new date picker which we call as 'techmights-datefield' looks as follows.


The user can click on "Not Provided" if the s/he doesn't wants to enter a date. The "Not Provided" value will be considered valid for this field.


The code for customized date picker and date field controls is as follows.

DatePicker.js
Ext.define('TMS.view.DatePicker',
{
	extend	: 'Ext.picker.Date',
	
	NPBtn	: undefined,
	NPText	: "Not Provided",
	
	 renderTpl: [
        '<div id="{id}-innerEl" role="grid">',
            '<div role="presentation" class="{baseCls}-header">',
                 // the href attribute is required for the :hover selector to work in IE6/7/quirks
                '<a id="{id}-prevEl" class="{baseCls}-prev {baseCls}-arrow" href="#" role="button" title="{prevText}" hidefocus="on" ></a>',
                '<div class="{baseCls}-month" id="{id}-middleBtnEl">{%this.renderMonthBtn(values, out)%}</div>',
                 // the href attribute is required for the :hover selector to work in IE6/7/quirks
                '<a id="{id}-nextEl" class="{baseCls}-next {baseCls}-arrow" href="#" role="button" title="{nextText}" hidefocus="on" ></a>',
            '</div>',
            '<table id="{id}-eventEl" class="{baseCls}-inner" cellspacing="0" role="grid">',
                '<thead role="presentation"><tr role="row">',
                    '<tpl for="dayNames">',
                        '<th role="columnheader" class="{parent.baseCls}-column-header" title="{.}">',
                            '<div class="{parent.baseCls}-column-header-inner">{.:this.firstInitial}</div>',
                        '</th>',
                    '</tpl>',
                '</tr></thead>',
                '<tbody role="presentation"><tr role="row">',
                    '<tpl for="days">',
                        '{#:this.isEndOfWeek}',
                        '<td role="gridcell" id="{[Ext.id()]}">',
                            // the href attribute is required for the :hover selector to work in IE6/7/quirks
                            '<a role="presentation" hidefocus="on" class="{parent.baseCls}-date" href="#"></a>',
                        '</td>',
                    '</tpl>',
                '</tr></tbody>',
            '</table>',
			'<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer">{%this.renderNPBtn(values, out)%}',
            '<tpl if="showToday">',
                '{%this.renderTodayBtn(values, out)%}',
            '</tpl>',
			'</div>',
        '</div>',
        {
            firstInitial: function(value) {
                return Ext.picker.Date.prototype.getDayInitial(value);
            },
            isEndOfWeek: function(value) {
                // convert from 1 based index to 0 based
                // by decrementing value once.
                value--;
                var end = value % 7 === 0 && value !== 0;
                return end ? '</tr><tr role="row">' : '';
            },
            renderTodayBtn: function(values, out) {
                Ext.DomHelper.generateMarkup(values.$comp.todayBtn.getRenderTree(), out);
            },
            renderMonthBtn: function(values, out) {
                Ext.DomHelper.generateMarkup(values.$comp.monthBtn.getRenderTree(), out);
            },
			
			renderNPBtn: function(values, out) {
                Ext.DomHelper.generateMarkup(values.$comp.NPBtn.getRenderTree(), out);
            },
        }
    ],
	
	beforeRender: function () 
	{
		var me = this;
		
		 me.NPBtn = new Ext.button.Button({
                ownerCt: me,
                ownerLayout: me.getComponentLayout(),
                text: me.NPText,
                tooltip: me.NPBtn,
                tooltipType: 'title',
                handler: me.selectNP,
                scope: me
            });
		
		me.callParent();
	},
	
	finishRenderChildren: function () {
        var me = this;        
        me.callParent();
        me.NPBtn.finishRender();
    },
	
	/**
     * Sets the value to "Not Provided".
     * @return {Ext.picker.Date} this
     */
    selectNP : function(){
        var me = this,
            btn = me.NPBtn,
            handler = me.handler;

        if(btn && !btn.disabled){
            me.setValue(me.NPText);
            me.fireEvent('select', me, me.value);
            if (handler) {
                handler.call(me.scope || me, me, me.value);
            }
            me.onSelect();
        }
        return me;
    },
		
	/**
     * Sets the value of the date field
     * @param {Date} value The date to set or "Not Provided"
     * @return {Ext.picker.Date} this
     */
    setValue : function(value){
	
		if(value != this.NPText)
			this.value = Ext.Date.clearTime(value, true);
		else
			this.value = value;
        return this.update(this.value);
    },

	
	/**
     * Update the contents of the picker
     * @private
     * @param {Date} date The new date or "Not Provided"
     * @param {Boolean} forceRefresh True to force a full refresh
     */
    update : function(date, forceRefresh)
	{
        var me = this,
        active = me.activeDate;

        if (me.rendered) 
		{
			if(date != me.NPText)
			{
				me.activeDate = date;
				if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
					me.selectedUpdate(date, active);
				} else {
					me.fullUpdate(date, active);
				}
			}
        }
        return me;
    }
});

Datefield.js
Ext.define('TMS.view.DateField',
{
	extend	: 'Ext.form.field.Date',
	alias	: 'widget.techmights-datefield',
	requires: ['TMS.view.DatePicker'],
	NPText	: "Not Provided",
	
	createPicker: function() {
        var me = this,
            format = Ext.String.format;

        return new TMS.view.DatePicker({
            pickerField: me,
            ownerCt: me.ownerCt,
            renderTo: document.body,
            floating: true,
            hidden: true,
            focusOnShow: true,
            minDate: me.minValue,
            maxDate: me.maxValue,
            disabledDatesRE: me.disabledDatesRE,
            disabledDatesText: me.disabledDatesText,
            disabledDays: me.disabledDays,
            disabledDaysText: me.disabledDaysText,
            format: me.format,
            showToday: me.showToday,
            startDay: me.startDay,
            minText: format(me.minText, me.formatDate(me.minValue)),
            maxText: format(me.maxText, me.formatDate(me.maxValue)),
            listeners: {
                scope: me,
                select: me.onSelect
            },
            keyNavConfig: {
                esc: function() {
                    me.collapse();
                }
            }
        });
    },
	
	rawToValue: function(rawValue) {
        
		if(rawValue === this.NPText)
			return rawValue;
		
		return this.parseDate(rawValue) || rawValue || null;
    },

    valueToRaw: function(value) {
		if(value === this.NPText)
			return value;
	
        return this.formatDate(this.parseDate(value));
    },
	
	 getErrors: function(value) {
		var me = this;
		var  errors = me.callParent(arguments);		
		if(!Ext.isEmpty(value) && value.toUpperCase() == me.NPText.toUpperCase())
			return ((errors.length>2)	? [errors[0]] : [] );
		
		return errors;	
	 }
});

Do you like this article?