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.
DatePicker.js
Datefield.js
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; } });