055 extjs6的DateTimeField
不知道为什么Extjs中始终没有日期和时间结合在一起的控件,这样就有了各个版本的自定义控件,但是随着版本的更新都不太兼容。在网上找了一些DateTimeField的参考过后,发现要完全的在Extjs6中实现,必须要重新生成一个DateTime的选择控件的类。网上大多数的类都是继承自
'Ext.picker.Date'
,经过一些试验过后,发现这个类的
在beforeRender的时候,需要建立这三个数值field和okbtn的实例。
再增加一个ok按钮的执行事件。
然后还需要修改一些函数:
以上是DateTimePicker的控件,还需要做一个DateTimeField的控件。
privates 属性不能被很好的继承,因此只能找到Ext.picker.Date的源码,重新拷贝一份来进行修改了。下面来讲解一下主要的更改地方和更改的说明。(源码的下载在最后)
renderTpl : [
'<div id="{id}-innerEl" data-ref="innerEl" role="presentation">',
'<div class="{baseCls}-header">',
'<div id="{id}-prevEl" data-ref="prevEl" class="{baseCls}-prev {baseCls}-arrow" role="presentation" title="{prevText}"></div>',
'<div id="{id}-middleBtnEl" data-ref="middleBtnEl" class="{baseCls}-month" role="heading">{%this.renderMonthBtn(values, out)%}</div>',
'<div id="{id}-nextEl" data-ref="nextEl" class="{baseCls}-next {baseCls}-arrow" role="presentation" title="{nextText}"></div>',
'</div>',
'<table role="grid" id="{id}-eventEl" data-ref="eventEl" class="{baseCls}-inner" cellspacing="0" tabindex="0" aria-readonly="true">',
'<thead>',
'<tr role="row">',
'<tpl for="dayNames">',
'<th role="columnheader" class="{parent.baseCls}-column-header" aria-label="{.}">',
'<div role="presentation" class="{parent.baseCls}-column-header-inner">{.:this.firstInitial}</div>',
'</th>',
'</tpl>',
'</tr>',
'</thead>',
'<tbody>',
'<tr role="row">',
'<tpl for="days">',
'{#:this.isEndOfWeek}',
'<td role="gridcell">',
'<div hidefocus="on" class="{parent.baseCls}-date"></div>',
'</td>',
'</tpl>',
'</tr>',
'</tbody>',
'</table>',
// 指定时分秒渲染框架
'<table id="{id}-timeEl" style="table-layout:auto;margin:0 auto;width:90%;text-align:center;" class="x-datepicker-inner" cellspacing="0">',
'<tbody><tr>',
'<td width="30%">{%this.renderHourField(values,out)%}</td>',
'<td width="35%">{%this.renderMinuteField(values,out)%}</td>',
'<td width="35%">{%this.renderSecondField(values,out)%}</td>',
'</tr></tbody>',
'</table>',
// 上面是新增的
'<tpl if="showToday">',
'<div id="{id}-footerEl" data-ref="footerEl" role="presentation" class="{baseCls}-footer">',
// 增加了确认按钮
'{%this.renderOkBtn(values, out)%}',
// 上面是新增的
'{%this.renderTodayBtn(values, out)%}',
'</div>',
'</tpl>',
// These elements are used with Assistive Technologies such as
// screen readers
'<div id="{id}-todayText" class="' + Ext.baseCSSPrefix
+ 'hidden-clip">{todayText}.</div>',
'<div id="{id}-ariaMinText" class="' + Ext.baseCSSPrefix
+ 'hidden-clip">{ariaMinText}.</div>',
'<div id="{id}-ariaMaxText" class="' + Ext.baseCSSPrefix
+ 'hidden-clip">{ariaMaxText}.</div>',
'<div id="{id}-ariaDisabledDaysText" class="' + Ext.baseCSSPrefix
+ 'hidden-clip">{ariaDisabledDaysText}.</div>',
'<div id="{id}-ariaDisabledDatesText" class="'
+ Ext.baseCSSPrefix
+ 'hidden-clip">{ariaDisabledDatesText}.</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);
},
// 指定渲染方法调用
renderHourField : function(values, out) {
Ext.DomHelper.generateMarkup(values.$comp.hourField
.getRenderTree(), out);
},
renderMinuteField : function(values, out) {
Ext.DomHelper.generateMarkup(values.$comp.minuteField
.getRenderTree(), out);
},
renderSecondField : function(values, out) {
Ext.DomHelper.generateMarkup(values.$comp.secondField
.getRenderTree(), out);
},
renderOkBtn : function(values, out) {
Ext.DomHelper.generateMarkup(values.$comp.okBtn
.getRenderTree(), out);
}
// 以上四个函数是新增的
} ],
首先修改了
renderTpl 这个参数,在里面加入了时分秒的录入field和一个"确定"按钮。时分秒这三个录入字段占用整个宽度的90%,这样正好能适用于各个theme。
其次也是比较主要的一个步骤,是要将里面有些清除时间的函数修改掉。比如initComponent这个函数中。
initComponent: function() {
var me = this,
clearTime = Ext.Date.clearTime;
me.selectedCls = me.baseCls + '-selected';
me.disabledCellCls = me.baseCls + '-disabled';
me.prevCls = me.baseCls + '-prevday';
me.activeCls = me.baseCls + '-active';
me.cellCls = me.baseCls + '-cell';
me.nextCls = me.baseCls + '-prevday';
me.todayCls = me.baseCls + '-today';
if (!me.format) {
me.format = Ext.Date.defaultFormat;
}
if (!me.dayNames) {
me.dayNames = Ext.Date.dayNames;
}
me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
me.callParent();
me.value = me.value ? clearTime(me.value, true) : clearTime(new Date());
me.initDisabledDays();
},
上面倒数第二条语句,会清除时间,这个要去掉。因此要改成
me.value = me.value ? (me.value) : (new Date());
在beforeRender的时候,需要建立这三个数值field和okbtn的实例。
beforeRender : function() {
/*
* days array for looping through 6 full weeks (6 weeks * 7 days)
* Note that we explicitly force the size here so the template
* creates all the appropriate cells.
*/
var me = this, encode = Ext.String.htmlEncode, days = new Array(
me.numDays), today = Ext.Date.format(new Date(), me.format);
if (me.padding && !me.width) {
me.cacheWidth();
}
// 自己加的
me.hourField = new Ext.form.field.Number({
ownerCt : me,
ownerLayout : me.getComponentLayout(),
minValue : 0,
maxValue : 23,
step : 1,
width : '100%',
enableKeyEvents : true,
listeners : {
specialkey : function(field, e) {
if (e.getKey() == e.ENTER) {
e.stopEvent();
me.minuteField.focus(true);
}
}
}
});
me.minuteField = new Ext.form.field.Number({
ownerCt : me,
ownerLayout : me.getComponentLayout(),
minValue : 0,
maxValue : 59,
step : 1,
width : '100%',
labelWidth : 10,
fieldLabel : ' ',
enableKeyEvents : true,
listeners : {
specialkey : function(field, e) {
if (e.getKey() == e.ENTER) {
e.stopEvent();
me.secondField.focus(true);
}
}
}
});
me.secondField = new Ext.form.field.Number({
ownerCt : me,
ownerLayout : me.getComponentLayout(),
minValue : 0,
maxValue : 59,
step : 1,
width : '100%',
labelWidth : 10,
fieldLabel : ' ',
enableKeyEvents : true,
listeners : {
specialkey : function(field, e) {
if (e.getKey() == e.ENTER) {
e.stopEvent();
me.okBtn.focus(true);
}
}
}
});
// 自己加的
me.monthBtn = new Ext.button.Split({
ownerCt : me,
ownerLayout : me.getComponentLayout(),
text : '',
tooltip : me.monthYearText,
tabIndex : -1,
ariaRole : 'presentation',
listeners : {
click : me.doShowMonthPicker,
arrowclick : me.doShowMonthPicker,
scope : me
}
});
if (me.showToday) {
// 自己加的
me.okBtn = new Ext.button.Button({
ui : me.footerButtonUI,
ownerCt : me,
ownerLayout : me.getComponentLayout(),
text : me.okText,
handler : me.okHandler, // 确认按钮的事件委托
scope : me
});
// 自己加的
me.todayBtn = new Ext.button.Button({
ui : me.footerButtonUI,
ownerCt : me,
ownerLayout : me.getComponentLayout(),
text : Ext.String.format(me.todayText, today),
tooltip : Ext.String.format(me.todayTip, today),
tooltipType : 'title',
tabIndex : -1,
ariaRole : 'presentation',
handler : me.selectToday,
scope : me
});
}
me.callParent();
Ext.applyIf(me, {
renderData : {}
});
Ext.apply(me.renderData, {
dayNames : me.dayNames,
showToday : me.showToday,
prevText : encode(me.prevText),
nextText : encode(me.nextText),
todayText : encode(me.todayText),
ariaMinText : encode(me.ariaMinText),
ariaMaxText : encode(me.ariaMaxText),
ariaDisabledDaysText : encode(me.ariaDisabledDaysText),
ariaDisabledDatesText : encode(me.ariaDisabledDatesText),
days : days
});
me.protoEl.unselectable();
},
再增加一个ok按钮的执行事件。
/**
* 确认 按钮触发的调用
*/
okHandler : function() {
var me = this, btn = me.okBtn;
if (btn && !btn.disabled) {
me.setValue(this.getValue());
me.fireEvent('select', me, me.value);
me.onSelect();
}
},
然后还需要修改一些函数:
setValue : function(value, isfixed) {
// If passed a null value just pass in a new date object.
var me = this;
// 这里一定要用 Ext.Date.clone ,不然不会触发 isDirty事件
this.value = Ext.Date.clone(value || new Date());
if (isfixed !== true) {
this.value.setHours(me.hourField.getValue());
this.value.setMinutes(me.minuteField.getValue());
this.value.setSeconds(me.secondField.getValue());
}
return this.update(this.value);
},
update : function(date, forceRefresh) {
var me = this, active = me.activeDate;
me.hourField.setValue(date.getHours());
me.minuteField.setValue(date.getMinutes());
me.secondField.setValue(date.getSeconds());
if (me.rendered) {
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;
},
具体还有一些修改请参见发布的源文件。
最后再定义一个本地化的文件。
Ext.define("Ext.locale.zh_CN.picker.DateTime", {
override : "app.ux.picker.DateTime",
todayText : "现在",
minText : "日期必须大于最小允许日期",
// update
maxText : "日期必须小于最大允许日期",
// update
disabledDaysText : "",
disabledDatesText : "",
nextText : '下个月 (Ctrl+Right)',
prevText : '上个月 (Ctrl+Left)',
monthYearText : '选择一个月 (Control+Up/Down 来改变年份)',
// update
todayTip : "{0}",
format : "y年m月d日",
ariaTitle : '{0}',
ariaTitleDateFormat : 'Y\u5e74m\u6708d\u65e5',
longDayFormat : 'Y\u5e74m\u6708d\u65e5',
monthYearFormat : 'Y\u5e74m\u6708',
getDayInitial : function(value) {
// Grab the last character
return value.substr(value.length - 1);
}
});
以上是DateTimePicker的控件,还需要做一个DateTimeField的控件。
Ext.define('app.module.widget.field.DateTime', {
extend : 'Ext.form.field.Date',
alias : 'widget.datetimefield',
requires : [ 'app.ux.picker.DateTime' ],
createPicker : function() {
var me = this, format = Ext.String.format;
return new app.ux.picker.DateTime({
pickerField : me,
floating : true,
preventRefocus : true,
hidden : true,
minDate : me.minValue,
maxDate : me.maxValue,
disabledDatesRE : me.disabledDatesRE,
disabledDatesText : me.disabledDatesText,
ariaDisabledDatesText : me.ariaDisabledDatesText,
disabledDays : me.disabledDays,
disabledDaysText : me.disabledDaysText,
ariaDisabledDaysText : me.ariaDisabledDaysText,
format : me.format,
showToday : me.showToday,
startDay : me.startDay,
minText : format(me.minText, me.formatDate(me.minValue)),
ariaMinText : format(me.ariaMinText, me.formatDate(me.minValue,
me.ariaFormat)),
maxText : format(me.maxText, me.formatDate(me.maxValue)),
ariaMaxText : format(me.ariaMaxText, me.formatDate(me.maxValue,
me.ariaFormat)),
listeners : {
scope : me,
select : me.onSelect,
tabout : me.onTabOut
},
keyNavConfig : {
esc : function() {
me.inputEl.focus();
me.collapse();
}
}
});
},
onExpand : function() {
var value = this.rawDate;
this.picker.setValue(Ext.isDate(value) ? value : new Date(), true);
}
})
看一下执行的结果:
开发过程中也遇到了不少问题,经过2天时间的排查和改进,操作都基本正常了。(此文件改用自 extjs6.0.2的源码,extjs4 和 5我没有试过,不一定能用)
源文件下载链接:https://pan.baidu.com/s/1qYJ52jU
(使用的时候注意你自己的文件的路径和类名的对应关系)。