ExtJS6.0扩展日期选择控件为也可以选择时间
PS:ExtJS自带的日期选择控件只能够选择日期,但是现在的需求需要精确到秒,所以在网上搜索了一些例子(大部分是4.0的)作为参考,然后改出了6.0可用的一个日期时间选择控件。
1、找到extjs6.0源代码中Picker文件路径下的Date.js脚本(路径:ext-6.0.0-gpl\ext-6.0.0\classic\classic\src\picker),拷贝一份出来命名为DateTimePicker.js
2、修改命名空间(把白色底的改成黑色底的命名空间以及别名,你也可以修改为自己存放该文件的路径,名称也可以自己命名)
3、定位到showToday模块
把原来的:'<div id="{id}-footerEl" data-ref="footerEl" role="presentation" class="{baseCls}-footer">{%this.renderTodayBtn(values, out)%}</div>',
替换成:'<div id="{id}-footerEl" role="presentation" style="background-color:#D9E5F3;border-top:0px solid #99BCE8;">{%this.renderHour(values, out)%}{%this.renderMinute(values, out)%}{%this.renderSecond(values, out)%}<center>{%this.renderOkQueDingBtn(values, out)%} {%this.renderTodayBtn(values, out)%}</center></div>',
也就是增加 时、分、秒、OK控件
1 '<tpl if="showToday">', 2 //'<div id="{id}-footerEl" data-ref="footerEl" role="presentation" class="{baseCls}-footer">{%this.renderTodayBtn(values, out)%}</div>', 3 '<div id="{id}-footerEl" role="presentation" style="background-color:#D9E5F3;border-top:0px solid #99BCE8;">{%this.renderHour(values, out)%}{%this.renderMinute(values, out)%}{%this.renderSecond(values, out)%}<center>{%this.renderOkQueDingBtn(values, out)%} {%this.renderTodayBtn(values, out)%}</center></div>', 4 '</tpl>',
4、添加时、分、秒、OK控件的渲染事件
1 longDay: function(value){ 2 return Ext.Date.format(value, this.longDayFormat); 3 }, 4 renderHour: function(values, out) { 5 out.push('<font style="float : left;">  </font>'); 6 Ext.DomHelper.generateMarkup(values.$comp.hour.getRenderTree(), out); 7 }, 8 renderMinute: function(values, out) { 9 out.push('<font style="float : left;font-weight:bold;"> :  </font>'); 10 Ext.DomHelper.generateMarkup(values.$comp.minute.getRenderTree(), out); 11 }, 12 renderSecond: function(values, out) { 13 out.push('<font style="float : left;font-weight:bold;"> :  </font>'); 14 Ext.DomHelper.generateMarkup(values.$comp.second.getRenderTree(), out); 15 }, 16 renderOkQueDingBtn: function(values, out) { 17 Ext.DomHelper.generateMarkup(values.$comp.okQueDingBtn.getRenderTree(), out); 18 },
5、在beforeRender事件中,添加时、分、秒、OK控件的事件
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 } }); me.hour = Ext.create('Ext.form.field.Number', { scope: me, ownerCt: me, editable : true, ownerLayout: me.getComponentLayout(), minValue: 0, maxValue: 23, width: 70, style : {float:"left"}, enableKeyEvents: true, listeners: { keyup: function(field, e){ if (field.getValue() > 23){ e.stopEvent(); field.setValue(23); } } } }); me.minute = Ext.create('Ext.form.field.Number', { scope: me, ownerCt: me, style : {float:"left"}, ownerLayout: me.getComponentLayout(), minValue: 0, maxValue: 59, editable : true, width: 70, enableKeyEvents: true, listeners: { keyup: function(field, e){ if (field.getValue() > 59){ e.stopEvent(); field.setValue(59); } } } }); me.second = Ext.create('Ext.form.field.Number', { scope: me, ownerCt: me, editable : true, style : {float:"left"}, ownerLayout: me.getComponentLayout(), minValue: 0, maxValue: 59, width: 70, enableKeyEvents: true, listeners: { keyup: function(field, e){ if (field.getValue() > 59){ e.stopEvent(); field.setValue(59); } } } }); me.okQueDingBtn = new Ext.button.Button({ ownerCt: me, ownerLayout: me.getComponentLayout(), text: me.okText, tooltip: me.okTip, tooltipType:'title', handler:me.okQueDingHandler,//确认按钮的事件委托 scope: me });
6、添加OK按钮处理事件
1 /** 2 * 确认 按钮触发的调用 3 */ 4 okQueDingHandler : function(){ 5 var me = this, 6 btn = me.okQueDingBtn; 7 8 if(btn && !btn.disabled){ 9 me.setValue(this.getValue()); 10 me.fireEvent('select', me, me.value); 11 me.onSelect(); 12 } 13 return me; 14 },
7、在update事件中添加更新时间的事件
1 date.setHours(me.hour.getValue()); 2 date.setMinutes(me.minute.getValue()); 3 date.setSeconds(me.second.getValue());
8、在beforeDestroy释放事件中添加释放OK按钮的事件
1 beforeDestroy: function() { 2 var me = this; 3 4 if (me.rendered) { 5 Ext.destroy( 6 me.keyNav, 7 me.monthPicker, 8 me.monthBtn, 9 me.nextRepeater, 10 me.prevRepeater, 11 me.todayBtn, 12 me.okQueDingBtn, 13 me.todayElSpan 14 ); 15 delete me.textNodes; 16 delete me.cells.elements; 17 } 18 me.callParent(); 19 },
9、添加其他释放事件
1 finishRenderChildren: function () { 2 var me = this; 3 4 me.callParent(); 5 me.monthBtn.finishRender(); 6 me.okQueDingBtn.finishRender(); 7 if (me.showToday) { 8 me.todayBtn.finishRender(); 9 } 10 //添加时间相关 11 this.hour.finishRender(); 12 this.minute.finishRender(); 13 this.second.finishRender(); 14 }, 15
10、到此Date.js脚本就改造完毕了下面是完整的DateTimePicker.js脚本
1 /** 2 * A date picker. This class is used by the Ext.form.field.Date field to allow browsing and selection of valid 3 * dates in a popup next to the field, but may also be used with other components. 4 * 5 * Typically you will need to implement a handler function to be notified when the user chooses a date from the picker; 6 * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method. 7 * 8 * By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate}, 9 * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs. 10 * 11 * All the string values documented below may be overridden by including an Ext locale file in your page. 12 * 13 * @example 14 * Ext.create('Ext.panel.Panel', { 15 * title: 'Choose a future date:', 16 * width: 200, 17 * bodyPadding: 10, 18 * renderTo: Ext.getBody(), 19 * items: [{ 20 * xtype: 'datepicker', 21 * minDate: new Date(), 22 * handler: function(picker, date) { 23 * // do something with the selected date 24 * } 25 * }] 26 * }); 27 */ 28 Ext.define('Ext.ux.DateTimePicker', { 29 extend: 'Ext.Component', 30 alias: 'widget.datetimepicker', 31 alternateClassName: 'Ext.DateTimePicker', 32 requires: [ 33 'Ext.XTemplate', 34 'Ext.button.Button', 35 'Ext.button.Split', 36 'Ext.util.ClickRepeater', 37 'Ext.util.KeyNav', 38 'Ext.fx.Manager', 39 'Ext.picker.Month' 40 ], 41 42 //<locale> 43 /** 44 * @cfg {String} todayText 45 * The text to display on the button that selects the current date 46 */ 47 todayText: 'Today', 48 //添加确定按钮 49 okText:'Ok', 50 okTip:'Ok', 51 //</locale> 52 53 //<locale> 54 /** 55 * @cfg {String} ariaTitle 56 * The text to display for the aria title 57 */ 58 ariaTitle: 'Date Picker: {0}', 59 //</locale> 60 61 //<locale> 62 /** 63 * @cfg {String} ariaTitleDateFormat 64 * The date format to display for the current value in the {@link #ariaTitle} 65 */ 66 ariaTitleDateFormat: 'F d', 67 //</locale> 68 69 /** 70 * @cfg {Function} handler 71 * Optional. A function that will handle the select event of this picker. The handler is passed the following 72 * parameters: 73 * 74 * - `picker` : Ext.picker.Date 75 * 76 * This Date picker. 77 * 78 * - `date` : Date 79 * 80 * The selected date. 81 */ 82 83 /** 84 * @cfg {Object} scope 85 * The scope (`this` reference) in which the `{@link #handler}` function will be called. 86 * 87 * Defaults to this DatePicker instance. 88 */ 89 90 //<locale> 91 /** 92 * @cfg {String} todayTip 93 * A string used to format the message for displaying in a tooltip over the button that selects the current date. 94 * The `{0}` token in string is replaced by today's date. 95 */ 96 todayTip: '{0} (Spacebar)', 97 //</locale> 98 99 //<locale> 100 /** 101 * @cfg {String} minText 102 * The error text to display if the minDate validation fails. 103 */ 104 minText: 'This date is before the minimum date', 105 //</locale> 106 107 //<locale> 108 /** 109 * @cfg {String} ariaMinText The text that will be announced by Assistive Technologies 110 * such as screen readers when user is navigating to the cell which date is less than 111 * {@link #minDate}. 112 */ 113 ariaMinText: "This date is before the minimum date", 114 //</locale> 115 116 //<locale> 117 /** 118 * @cfg {String} maxText 119 * The error text to display if the maxDate validation fails. 120 */ 121 maxText: 'This date is after the maximum date', 122 //</locale> 123 124 //<locale> 125 /** 126 * @cfg {String} ariaMaxText The text that will be announced by Assistive Technologies 127 * such as screen readers when user is navigating to the cell which date is later than 128 * {@link #maxDate}. 129 */ 130 ariaMaxText: "This date is after the maximum date", 131 //</locale> 132 133 /** 134 * @cfg {String} format 135 * The default date format string which can be overriden for localization support. The format must be valid 136 * according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}). 137 */ 138 139 //<locale> 140 /** 141 * @cfg {String} disabledDaysText 142 * The tooltip to display when the date falls on a disabled day. 143 */ 144 disabledDaysText: 'Disabled', 145 //</locale> 146 147 //<locale> 148 /** 149 * @cfg {String} ariaDisabledDaysText The text that Assistive Technologies such as screen readers 150 * will announce when the date falls on a disabled day of week. 151 */ 152 ariaDisabledDaysText: "This day of week is disabled", 153 //</locale> 154 155 //<locale> 156 /** 157 * @cfg {String} disabledDatesText 158 * The tooltip text to display when the date falls on a disabled date. 159 */ 160 disabledDatesText: 'Disabled', 161 //</locale> 162 163 //<locale> 164 /** 165 * @cfg {String} ariaDisabledDatesText The text that Assistive Technologies such as screen readers 166 * will announce when the date falls on a disabled date. 167 */ 168 ariaDisabledDatesText: "This date is disabled", 169 170 //</locale> 171 /** 172 * @cfg {String[]} monthNames 173 * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames) 174 * @deprecated This config is deprecated. In future the month names will be retrieved from {@link Ext.Date} 175 */ 176 177 /** 178 * @cfg {String[]} dayNames 179 * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames) 180 * @deprecated This config is deprecated. In future the day names will be retrieved from {@link Ext.Date} 181 */ 182 183 //<locale> 184 /** 185 * @cfg {String} nextText 186 * The next month navigation button tooltip 187 */ 188 nextText: 'Next Month (Control+Right)', 189 //</locale> 190 191 //<locale> 192 /** 193 * @cfg {String} prevText 194 * The previous month navigation button tooltip 195 */ 196 prevText: 'Previous Month (Control+Left)', 197 //</locale> 198 199 //<locale> 200 /** 201 * @cfg {String} monthYearText 202 * The header month selector tooltip 203 */ 204 monthYearText: 'Choose a month (Control+Up/Down to move years)', 205 //</locale> 206 207 //<locale> 208 /** 209 * @cfg {String} monthYearFormat 210 * The date format for the header month 211 */ 212 monthYearFormat: 'F Y', 213 //</locale> 214 215 //<locale> 216 /** 217 * @cfg {Number} [startDay=undefined] 218 * Day index at which the week should begin, 0-based. 219 * 220 * Defaults to `0` (Sunday). 221 */ 222 startDay: 0, 223 //</locale> 224 225 //<locale> 226 /** 227 * @cfg {Boolean} showToday 228 * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar that 229 * selects the current date. 230 */ 231 showToday: true, 232 //</locale> 233 234 /** 235 * @cfg {Date} [minDate=null] 236 * Minimum allowable date (JavaScript date object) 237 */ 238 239 /** 240 * @cfg {Date} [maxDate=null] 241 * Maximum allowable date (JavaScript date object) 242 */ 243 244 /** 245 * @cfg {Number[]} [disabledDays=null] 246 * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday. 247 */ 248 249 /** 250 * @cfg {RegExp} [disabledDatesRE=null] 251 * JavaScript regular expression used to disable a pattern of dates. The {@link #disabledDates} 252 * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the 253 * disabledDates value. 254 */ 255 256 /** 257 * @cfg {String[]} disabledDates 258 * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular expression so 259 * they are very powerful. Some examples: 260 * 261 * - ['03/08/2003', '09/16/2003'] would disable those exact dates 262 * - ['03/08', '09/16'] would disable those days for every year 263 * - ['^03/08'] would only match the beginning (useful if you are using short years) 264 * - ['03/../2006'] would disable every day in March 2006 265 * - ['^03'] would disable every day in every March 266 * 267 * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order 268 * to support regular expressions, if you are using a date format that has '.' in it, you will have to escape the 269 * dot when restricting dates. For example: ['03\\.08\\.03']. 270 */ 271 272 /** 273 * @cfg {Boolean} disableAnim 274 * True to disable animations when showing the month picker. 275 */ 276 disableAnim: false, 277 278 /** 279 * @cfg {String} [baseCls='x-datepicker'] 280 * The base CSS class to apply to this components element. 281 */ 282 baseCls: Ext.baseCSSPrefix + 'datepicker', 283 284 /** 285 * @cfg {String} [selectedCls='x-datepicker-selected'] 286 * The class to apply to the selected cell. 287 */ 288 289 /** 290 * @cfg {String} [disabledCellCls='x-datepicker-disabled'] 291 * The class to apply to disabled cells. 292 */ 293 294 //<locale> 295 /** 296 * @cfg {String} longDayFormat 297 * The format for displaying a date in a longer format. 298 */ 299 longDayFormat: 'F d, Y', 300 //</locale> 301 302 /** 303 * @cfg {Object} keyNavConfig 304 * Specifies optional custom key event handlers for the {@link Ext.util.KeyNav} attached to this date picker. Must 305 * conform to the config format recognized by the {@link Ext.util.KeyNav} constructor. Handlers specified in this 306 * object will replace default handlers of the same name. 307 */ 308 309 /** 310 * @cfg {String} 311 * The {@link Ext.button.Button#ui} to use for the date picker's footer buttons. 312 */ 313 footerButtonUI: 'default', 314 315 isDatePicker: true, 316 317 ariaRole: 'region', 318 focusable: true, 319 320 childEls: [ 321 'innerEl', 'eventEl', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl' 322 ], 323 324 border: true, 325 326 /** 327 * @cfg 328 * @inheritdoc 329 */ 330 renderTpl: [ 331 '<div id="{id}-innerEl" data-ref="innerEl" role="presentation">', 332 '<div class="{baseCls}-header">', 333 '<div id="{id}-prevEl" data-ref="prevEl" class="{baseCls}-prev {baseCls}-arrow" role="presentation" title="{prevText}"></div>', 334 '<div id="{id}-middleBtnEl" data-ref="middleBtnEl" class="{baseCls}-month" role="heading">{%this.renderMonthBtn(values, out)%}</div>', 335 '<div id="{id}-nextEl" data-ref="nextEl" class="{baseCls}-next {baseCls}-arrow" role="presentation" title="{nextText}"></div>', 336 '</div>', 337 '<table role="grid" id="{id}-eventEl" data-ref="eventEl" class="{baseCls}-inner" cellspacing="0" tabindex="0">', 338 '<thead>', 339 '<tr role="row">', 340 '<tpl for="dayNames">', 341 '<th role="columnheader" class="{parent.baseCls}-column-header" aria-label="{.}">', 342 '<div role="presentation" class="{parent.baseCls}-column-header-inner">{.:this.firstInitial}</div>', 343 '</th>', 344 '</tpl>', 345 '</tr>', 346 '</thead>', 347 '<tbody>', 348 '<tr role="row">', 349 '<tpl for="days">', 350 '{#:this.isEndOfWeek}', 351 '<td role="gridcell">', 352 '<div hidefocus="on" class="{parent.baseCls}-date"></div>', 353 '</td>', 354 '</tpl>', 355 '</tr>', 356 '</tbody>', 357 '</table>', 358 '<tpl if="showToday">', 359 //'<div id="{id}-footerEl" data-ref="footerEl" role="presentation" class="{baseCls}-footer">{%this.renderTodayBtn(values, out)%}</div>', 360 '<div id="{id}-footerEl" role="presentation" style="background-color:#D9E5F3;border-top:0px solid #99BCE8;">{%this.renderHour(values, out)%}{%this.renderMinute(values, out)%}{%this.renderSecond(values, out)%}<center>{%this.renderOkQueDingBtn(values, out)%} {%this.renderTodayBtn(values, out)%}</center></div>', 361 '</tpl>', 362 // These elements are used with Assistive Technologies such as screen readers 363 '<div id="{id}-todayText" class="' + Ext.baseCSSPrefix + 'hidden-clip">{todayText}.</div>', 364 '<div id="{id}-todayText" class="' + Ext.baseCSSPrefix + 'hidden-clip">{okText}.</div>', 365 '<div id="{id}-ariaMinText" class="' + Ext.baseCSSPrefix + 'hidden-clip">{ariaMinText}.</div>', 366 '<div id="{id}-ariaMaxText" class="' + Ext.baseCSSPrefix + 'hidden-clip">{ariaMaxText}.</div>', 367 '<div id="{id}-ariaDisabledDaysText" class="' + Ext.baseCSSPrefix + 'hidden-clip">{ariaDisabledDaysText}.</div>', 368 '<div id="{id}-ariaDisabledDatesText" class="' + Ext.baseCSSPrefix + 'hidden-clip">{ariaDisabledDatesText}.</div>', 369 '</div>', 370 { 371 firstInitial: function(value) { 372 return Ext.picker.Date.prototype.getDayInitial(value); 373 }, 374 isEndOfWeek: function(value) { 375 // convert from 1 based index to 0 based 376 // by decrementing value once. 377 value--; 378 var end = value % 7 === 0 && value !== 0; 379 return end ? '</tr><tr role="row">' : ''; 380 }, 381 longDay: function(value){ 382 return Ext.Date.format(value, this.longDayFormat); 383 }, 384 renderHour: function(values, out) { 385 out.push('<font style="float : left;">  </font>'); 386 Ext.DomHelper.generateMarkup(values.$comp.hour.getRenderTree(), out); 387 }, 388 renderMinute: function(values, out) { 389 out.push('<font style="float : left;font-weight:bold;"> :  </font>'); 390 Ext.DomHelper.generateMarkup(values.$comp.minute.getRenderTree(), out); 391 }, 392 renderSecond: function(values, out) { 393 out.push('<font style="float : left;font-weight:bold;"> :  </font>'); 394 Ext.DomHelper.generateMarkup(values.$comp.second.getRenderTree(), out); 395 }, 396 renderOkQueDingBtn: function(values, out) { 397 Ext.DomHelper.generateMarkup(values.$comp.okQueDingBtn.getRenderTree(), out); 398 }, 399 renderTodayBtn: function(values, out) { 400 Ext.DomHelper.generateMarkup(values.$comp.todayBtn.getRenderTree(), out); 401 }, 402 renderMonthBtn: function(values, out) { 403 Ext.DomHelper.generateMarkup(values.$comp.monthBtn.getRenderTree(), out); 404 } 405 } 406 ], 407 408 // Default value used to initialise each date in the DatePicker. 409 // __Note:__ 12 noon was chosen because it steers well clear of all DST timezone changes. 410 initHour: 12, // 24-hour format 411 412 numDays: 42, 413 414 /** 415 * @event select 416 * Fires when a date is selected 417 * @param {Ext.picker.Date} this DatePicker 418 * @param {Date} date The selected date 419 */ 420 421 /** 422 * @private 423 * @inheritdoc 424 */ 425 initComponent: function() { 426 var me = this, 427 clearTime = Ext.Date.clearTime; 428 429 me.selectedCls = me.baseCls + '-selected'; 430 me.disabledCellCls = me.baseCls + '-disabled'; 431 me.prevCls = me.baseCls + '-prevday'; 432 me.activeCls = me.baseCls + '-active'; 433 me.cellCls = me.baseCls + '-cell'; 434 me.nextCls = me.baseCls + '-prevday'; 435 me.todayCls = me.baseCls + '-today'; 436 437 438 if (!me.format) { 439 me.format = Ext.Date.defaultFormat; 440 } 441 if (!me.dayNames) { 442 me.dayNames = Ext.Date.dayNames; 443 } 444 me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay)); 445 446 me.callParent(); 447 448 me.value = me.value ? clearTime(me.value, true) : clearTime(new Date()); 449 450 me.initDisabledDays(); 451 }, 452 453 // Keep the tree structure correct for Ext.form.field.Picker input fields which poke a 'pickerField' reference down into their pop-up pickers. 454 getRefOwner: function() { 455 return this.pickerField || this.callParent(); 456 }, 457 458 getRefItems: function() { 459 var results = [], 460 monthBtn = this.monthBtn, 461 todayBtn = this.todayBtn; 462 463 if (monthBtn) { 464 results.push(monthBtn); 465 } 466 467 if (todayBtn) { 468 results.push(todayBtn); 469 } 470 471 return results; 472 }, 473 474 beforeRender: function() { 475 /* 476 * days array for looping through 6 full weeks (6 weeks * 7 days) 477 * Note that we explicitly force the size here so the template creates 478 * all the appropriate cells. 479 */ 480 var me = this, 481 encode = Ext.String.htmlEncode, 482 days = new Array(me.numDays), 483 today = Ext.Date.format(new Date(), me.format); 484 485 if (me.padding && !me.width) { 486 me.cacheWidth(); 487 } 488 489 me.monthBtn = new Ext.button.Split({ 490 ownerCt: me, 491 ownerLayout: me.getComponentLayout(), 492 text: '', 493 tooltip: me.monthYearText, 494 tabIndex: -1, 495 ariaRole: 'presentation', 496 listeners: { 497 click: me.doShowMonthPicker, 498 arrowclick: me.doShowMonthPicker, 499 scope: me 500 } 501 }); 502 503 me.hour = Ext.create('Ext.form.field.Number', { 504 scope: me, 505 ownerCt: me, 506 editable : true, 507 ownerLayout: me.getComponentLayout(), 508 minValue: 0, 509 maxValue: 23, 510 width: 70, 511 style : {float:"left"}, 512 enableKeyEvents: true, 513 listeners: { 514 keyup: function(field, e){ 515 if (field.getValue() > 23){ 516 e.stopEvent(); 517 field.setValue(23); 518 } 519 } 520 } 521 }); 522 523 me.minute = Ext.create('Ext.form.field.Number', { 524 scope: me, 525 ownerCt: me, 526 style : {float:"left"}, 527 ownerLayout: me.getComponentLayout(), 528 minValue: 0, 529 maxValue: 59, 530 editable : true, 531 width: 70, 532 enableKeyEvents: true, 533 listeners: { 534 keyup: function(field, e){ 535 if (field.getValue() > 59){ 536 e.stopEvent(); 537 field.setValue(59); 538 } 539 } 540 } 541 }); 542 543 me.second = Ext.create('Ext.form.field.Number', { 544 scope: me, 545 ownerCt: me, 546 editable : true, 547 style : {float:"left"}, 548 ownerLayout: me.getComponentLayout(), 549 minValue: 0, 550 maxValue: 59, 551 width: 70, 552 enableKeyEvents: true, 553 listeners: { 554 keyup: function(field, e){ 555 if (field.getValue() > 59){ 556 e.stopEvent(); 557 field.setValue(59); 558 } 559 } 560 } 561 }); 562 me.okQueDingBtn = new Ext.button.Button({ 563 ownerCt: me, 564 ownerLayout: me.getComponentLayout(), 565 text: me.okText, 566 tooltip: me.okTip, 567 tooltipType:'title', 568 handler:me.okQueDingHandler,//确认按钮的事件委托 569 scope: me 570 }); 571 if (me.showToday) { 572 me.todayBtn = new Ext.button.Button({ 573 ui: me.footerButtonUI, 574 ownerCt: me, 575 ownerLayout: me.getComponentLayout(), 576 text: Ext.String.format(me.todayText, today), 577 tooltip: Ext.String.format(me.todayTip, today), 578 tooltipType: 'title', 579 tabIndex: -1, 580 ariaRole: 'presentation', 581 handler: me.selectToday, 582 scope: me 583 }); 584 } 585 586 me.callParent(); 587 588 Ext.applyIf(me, { 589 renderData: {} 590 }); 591 592 Ext.apply(me.renderData, { 593 dayNames: me.dayNames, 594 showToday: me.showToday, 595 prevText: encode(me.prevText), 596 nextText: encode(me.nextText), 597 todayText: encode(me.todayText), 598 ariaMinText: encode(me.ariaMinText), 599 ariaMaxText: encode(me.ariaMaxText), 600 ariaDisabledDaysText: encode(me.ariaDisabledDaysText), 601 ariaDisabledDatesText: encode(me.ariaDisabledDatesText), 602 days: days 603 }); 604 605 me.protoEl.unselectable(); 606 }, 607 608 cacheWidth: function() { 609 var me = this, 610 padding = me.parseBox(me.padding), 611 widthEl = Ext.getBody().createChild({ 612 cls: me.baseCls + ' ' + me.borderBoxCls, 613 style: 'position:absolute;top:-1000px;left:-1000px;' 614 }); 615 616 me.self.prototype.width = widthEl.getWidth() + padding.left + padding.right; 617 widthEl.destroy(); 618 }, 619 620 /** 621 * @inheritdoc 622 * @private 623 */ 624 onRender: function(container, position) { 625 var me = this; 626 627 me.callParent(arguments); 628 629 me.cells = me.eventEl.select('tbody td'); 630 me.textNodes = me.eventEl.query('tbody td div'); 631 632 me.eventEl.set({ 'aria-labelledby': me.monthBtn.id }); 633 634 me.mon(me.eventEl, { 635 scope: me, 636 mousewheel: me.handleMouseWheel, 637 click: { 638 fn: me.handleDateClick, 639 delegate: 'div.' + me.baseCls + '-date' 640 } 641 }); 642 643 }, 644 645 /** 646 * @inheritdoc 647 * @private 648 */ 649 initEvents: function(){ 650 var me = this, 651 pickerField = me.pickerField, 652 eDate = Ext.Date, 653 day = eDate.DAY; 654 655 me.callParent(); 656 657 // If we're part of a date field, don't allow us to focus, the field will 658 // handle that. If we are standalone, then allow the default behaviour 659 // to occur to receive focus 660 if (pickerField) { 661 me.el.on('mousedown', me.onMouseDown, me); 662 } 663 664 me.prevRepeater = new Ext.util.ClickRepeater(me.prevEl, { 665 handler: me.showPrevMonth, 666 scope: me, 667 preventDefault: true, 668 stopDefault: true 669 }); 670 671 me.nextRepeater = new Ext.util.ClickRepeater(me.nextEl, { 672 handler: me.showNextMonth, 673 scope: me, 674 preventDefault: true, 675 stopDefault: true 676 }); 677 678 me.keyNav = new Ext.util.KeyNav(me.eventEl, Ext.apply({ 679 scope: me, 680 681 left: function(e) { 682 if (e.ctrlKey) { 683 me.showPrevMonth(); 684 } else { 685 me.update(eDate.add(me.activeDate, day, -1)); 686 } 687 }, 688 689 right: function(e){ 690 if (e.ctrlKey) { 691 me.showNextMonth(); 692 } else { 693 me.update(eDate.add(me.activeDate, day, 1)); 694 } 695 }, 696 697 up: function(e) { 698 if (e.ctrlKey) { 699 me.showNextYear(); 700 } else { 701 me.update(eDate.add(me.activeDate, day, -7)); 702 } 703 }, 704 705 down: function(e) { 706 if (e.ctrlKey) { 707 me.showPrevYear(); 708 } else { 709 me.update(eDate.add(me.activeDate, day, 7)); 710 } 711 }, 712 713 pageUp: function(e) { 714 if (e.ctrlKey) { 715 me.showPrevYear(); 716 } else { 717 me.showPrevMonth(); 718 } 719 }, 720 721 pageDown: function(e) { 722 if (e.ctrlKey) { 723 me.showNextYear(); 724 } else { 725 me.showNextMonth(); 726 } 727 }, 728 729 tab: function(e) { 730 // When the picker is floating and attached to an input field, its 731 // 'select' handler will focus the inputEl so when navigation happens 732 // it does so as if the input field was focused all the time. 733 // This is the desired behavior and we try not to interfere with it 734 // in the picker itself, see below. 735 me.handleTabClick(e); 736 737 // Allow default behaviour of TAB - it MUST be allowed to navigate. 738 return true; 739 }, 740 741 enter: function(e) { 742 me.handleDateClick(e, me.activeCell.firstChild); 743 }, 744 745 space: function() { 746 me.setValue(new Date(me.activeCell.firstChild.dateValue)); 747 var startValue = me.startValue, 748 value = me.value, 749 pickerValue; 750 751 if (pickerField) { 752 pickerValue = pickerField.getValue(); 753 if (pickerValue && startValue && pickerValue.getTime() === value.getTime()) { 754 pickerField.setValue(startValue); 755 } else { 756 pickerField.setValue(value); 757 } 758 } 759 }, 760 761 home: function(e) { 762 me.update(eDate.getFirstDateOfMonth(me.activeDate)); 763 }, 764 765 end: function(e) { 766 me.update(eDate.getLastDateOfMonth(me.activeDate)); 767 } 768 }, me.keyNavConfig)); 769 770 if (me.disabled) { 771 me.syncDisabled(true); 772 } 773 me.update(me.value); 774 }, 775 776 onMouseDown: function(e) { 777 e.preventDefault(); 778 }, 779 780 handleTabClick: function (e) { 781 var me = this, 782 t = me.getSelectedDate(me.activeDate), 783 handler = me.handler; 784 785 // The following code is like handleDateClick without the e.stopEvent() 786 if (!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)) { 787 me.setValue(new Date(t.dateValue)); 788 me.fireEvent('select', me, me.value); 789 if (handler) { 790 handler.call(me.scope || me, me, me.value); 791 } 792 me.onSelect(); 793 } 794 }, 795 796 getSelectedDate: function (date) { 797 var me = this, 798 t = date.getTime(), 799 cells = me.cells, 800 cls = me.selectedCls, 801 cellItems = cells.elements, 802 cLen = cellItems.length, 803 cell, c; 804 805 cells.removeCls(cls); 806 807 for (c = 0; c < cLen; c++) { 808 cell = cellItems[c].firstChild; 809 if (cell.dateValue === t) { 810 return cell; 811 } 812 } 813 return null; 814 }, 815 816 /** 817 * Setup the disabled dates regex based on config options 818 * @private 819 */ 820 initDisabledDays: function() { 821 var me = this, 822 dd = me.disabledDates, 823 re = '(?:', 824 len, 825 d, dLen, dI; 826 827 if(!me.disabledDatesRE && dd){ 828 len = dd.length - 1; 829 830 dLen = dd.length; 831 832 for (d = 0; d < dLen; d++) { 833 dI = dd[d]; 834 835 re += Ext.isDate(dI) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(dI, me.format)) + '$' : dI; 836 if (d !== len) { 837 re += '|'; 838 } 839 } 840 841 me.disabledDatesRE = new RegExp(re + ')'); 842 } 843 }, 844 845 /** 846 * Replaces any existing disabled dates with new values and refreshes the DatePicker. 847 * @param {String[]/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config for 848 * details on supported values), or a JavaScript regular expression used to disable a pattern of dates. 849 * @return {Ext.picker.Date} this 850 */ 851 setDisabledDates: function(dd) { 852 var me = this; 853 854 if (Ext.isArray(dd)) { 855 me.disabledDates = dd; 856 me.disabledDatesRE = null; 857 } else { 858 me.disabledDatesRE = dd; 859 } 860 me.initDisabledDays(); 861 me.update(me.value, true); 862 return me; 863 }, 864 865 /** 866 * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker. 867 * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details 868 * on supported values. 869 * @return {Ext.picker.Date} this 870 */ 871 setDisabledDays: function(dd) { 872 this.disabledDays = dd; 873 return this.update(this.value, true); 874 }, 875 876 /** 877 * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker. 878 * @param {Date} value The minimum date that can be selected 879 * @return {Ext.picker.Date} this 880 */ 881 setMinDate: function(dt) { 882 this.minDate = dt; 883 return this.update(this.value, true); 884 }, 885 886 /** 887 * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker. 888 * @param {Date} value The maximum date that can be selected 889 * @return {Ext.picker.Date} this 890 */ 891 setMaxDate: function(dt) { 892 this.maxDate = dt; 893 return this.update(this.value, true); 894 }, 895 896 /** 897 * Sets the value of the date field 898 * @param {Date} value The date to set 899 * @return {Ext.picker.Date} this 900 */ 901 setValue: function(value) { 902 // If passed a null value just pass in a new date object. 903 this.value = Ext.Date.clearTime(value || new Date(), true); 904 return this.update(this.value); 905 }, 906 907 /** 908 * Gets the current selected value of the date field 909 * @return {Date} The selected date 910 */ 911 getValue: function() { 912 return this.value; 913 }, 914 915 //<locale type="function"> 916 /** 917 * Gets a single character to represent the day of the week 918 * @return {String} The character 919 */ 920 getDayInitial: function(value) { 921 return value.substr(0,1); 922 }, 923 //</locale> 924 925 /** 926 * @inheritdoc 927 * @private 928 */ 929 onEnable: function() { 930 var me = this; 931 932 me.callParent(); 933 me.syncDisabled(false); 934 me.update(me.activeDate); 935 936 }, 937 938 /** 939 * @inheritdoc 940 * @private 941 */ 942 onShow: function() { 943 var me = this; 944 945 me.callParent(); 946 me.syncDisabled(false); 947 if (me.pickerField) { 948 me.startValue = me.pickerField.getValue(); 949 } 950 }, 951 952 /** 953 * @inheritdoc 954 * @private 955 */ 956 onHide: function() { 957 this.callParent(); 958 this.syncDisabled(true); 959 }, 960 961 /** 962 * @inheritdoc 963 * @private 964 */ 965 onDisable: function() { 966 this.callParent(); 967 this.syncDisabled(true); 968 }, 969 970 /** 971 * Get the current active date. 972 * @private 973 * @return {Date} The active date 974 */ 975 getActive: function(){ 976 return this.activeDate || this.value; 977 }, 978 979 /** 980 * Run any animation required to hide/show the month picker. 981 * @private 982 * @param {Boolean} isHide True if it's a hide operation 983 */ 984 runAnimation: function(isHide){ 985 var picker = this.monthPicker, 986 options = { 987 duration: 200, 988 callback: function() { 989 picker.setVisible(!isHide); 990 } 991 }; 992 993 if (isHide) { 994 picker.el.slideOut('t', options); 995 } else { 996 picker.el.slideIn('t', options); 997 } 998 }, 999 1000 /** 1001 * Hides the month picker, if it's visible. 1002 * @param {Boolean} [animate] Indicates whether to animate this action. If the animate 1003 * parameter is not specified, the behavior will use {@link #disableAnim} to determine 1004 * whether to animate or not. 1005 * @return {Ext.picker.Date} this 1006 */ 1007 hideMonthPicker: function(animate){ 1008 var me = this, 1009 picker = me.monthPicker; 1010 1011 if (picker && picker.isVisible()) { 1012 if (me.shouldAnimate(animate)) { 1013 me.runAnimation(true); 1014 } else { 1015 picker.hide(); 1016 } 1017 } 1018 return me; 1019 }, 1020 1021 doShowMonthPicker: function(){ 1022 // Wrap in an extra call so we can prevent the button 1023 // being passed as an animation parameter. 1024 this.showMonthPicker(); 1025 }, 1026 1027 doHideMonthPicker: function() { 1028 // Wrap in an extra call so we can prevent this 1029 // being passed as an animation parameter 1030 this.hideMonthPicker(); 1031 }, 1032 1033 /** 1034 * Show the month picker 1035 * @param {Boolean} [animate] Indicates whether to animate this action. If the animate 1036 * parameter is not specified, the behavior will use {@link #disableAnim} to determine 1037 * whether to animate or not. 1038 * @return {Ext.picker.Date} this 1039 */ 1040 showMonthPicker: function(animate) { 1041 var me = this, 1042 el = me.el, 1043 picker; 1044 1045 if (me.rendered && !me.disabled) { 1046 picker = me.createMonthPicker(); 1047 if (!picker.isVisible()) { 1048 picker.setValue(me.getActive()); 1049 picker.setSize(el.getSize()); 1050 1051 // Null out floatParent so that the [-1, -1] position is not made relative to this 1052 picker.floatParent = null; 1053 picker.setPosition(-el.getBorderWidth('l'), -el.getBorderWidth('t')); 1054 if (me.shouldAnimate(animate)) { 1055 me.runAnimation(false); 1056 } else { 1057 picker.show(); 1058 } 1059 } 1060 } 1061 return me; 1062 }, 1063 1064 /** 1065 * Checks whether a hide/show action should animate 1066 * @private 1067 * @param {Boolean} [animate] A possible animation value 1068 * @return {Boolean} Whether to animate the action 1069 */ 1070 shouldAnimate: function(animate) { 1071 return Ext.isDefined(animate) ? animate : !this.disableAnim; 1072 }, 1073 1074 /** 1075 * Create the month picker instance 1076 * @private 1077 * @return {Ext.picker.Month} picker 1078 */ 1079 createMonthPicker: function() { 1080 var me = this, 1081 picker = me.monthPicker; 1082 1083 if (!picker) { 1084 me.monthPicker = picker = new Ext.picker.Month({ 1085 renderTo: me.el, 1086 // We need to set the ownerCmp so that owns() can correctly 1087 // match up the component hierarchy so that focus does not leave 1088 // an owning picker field if/when this gets focus. 1089 ownerCmp: me, 1090 floating: true, 1091 padding: me.padding, 1092 shadow: false, 1093 small: me.showToday === false, 1094 footerButtonUI: me.footerButtonUI, 1095 listeners: { 1096 scope: me, 1097 cancelclick: me.onCancelClick, 1098 okclick: me.onOkClick, 1099 yeardblclick: me.onOkClick, 1100 monthdblclick: me.onOkClick 1101 } 1102 }); 1103 if (!me.disableAnim) { 1104 // hide the element if we're animating to prevent an initial flicker 1105 picker.el.setStyle('display', 'none'); 1106 } 1107 picker.hide(); 1108 me.on('beforehide', me.doHideMonthPicker, me); 1109 } 1110 return picker; 1111 }, 1112 1113 /** 1114 * Respond to an ok click on the month picker 1115 * @private 1116 */ 1117 onOkClick: function(picker, value) { 1118 var me = this, 1119 month = value[0], 1120 year = value[1], 1121 date = new Date(year, month, me.getActive().getDate()); 1122 1123 if (date.getMonth() !== month) { 1124 // 'fix' the JS rolling date conversion if needed 1125 date = Ext.Date.getLastDateOfMonth(new Date(year, month, 1)); 1126 } 1127 me.setValue(date); 1128 me.hideMonthPicker(); 1129 }, 1130 1131 /** 1132 * Respond to a cancel click on the month picker 1133 * @private 1134 */ 1135 onCancelClick: function() { 1136 this.selectedUpdate(this.activeDate); 1137 this.hideMonthPicker(); 1138 }, 1139 1140 /** 1141 * Show the previous month. 1142 * @param {Object} e 1143 * @return {Ext.picker.Date} this 1144 */ 1145 showPrevMonth: function(e) { 1146 return this.setValue(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1)); 1147 }, 1148 1149 /** 1150 * Show the next month. 1151 * @param {Object} e 1152 * @return {Ext.picker.Date} this 1153 */ 1154 showNextMonth: function(e) { 1155 return this.setValue(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1)); 1156 }, 1157 1158 /** 1159 * Show the previous year. 1160 * @return {Ext.picker.Date} this 1161 */ 1162 showPrevYear: function() { 1163 return this.setValue(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1)); 1164 }, 1165 1166 /** 1167 * Show the next year. 1168 * @return {Ext.picker.Date} this 1169 */ 1170 showNextYear: function() { 1171 return this.setValue(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1)); 1172 }, 1173 1174 /** 1175 * Respond to the mouse wheel event 1176 * @private 1177 * @param {Ext.event.Event} e 1178 */ 1179 handleMouseWheel: function(e) { 1180 e.stopEvent(); 1181 if(!this.disabled){ 1182 var delta = e.getWheelDelta(); 1183 if(delta > 0){ 1184 this.showPrevMonth(); 1185 } else if(delta < 0){ 1186 this.showNextMonth(); 1187 } 1188 } 1189 }, 1190 1191 /** 1192 * Respond to a date being clicked in the picker 1193 * @private 1194 * @param {Ext.event.Event} e 1195 * @param {HTMLElement} t 1196 */ 1197 handleDateClick: function(e, t) { 1198 var me = this, 1199 handler = me.handler; 1200 1201 e.stopEvent(); 1202 if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){ 1203 me.setValue(new Date(t.dateValue)); 1204 me.fireEvent('select', me, me.value); 1205 if (handler) { 1206 handler.call(me.scope || me, me, me.value); 1207 } 1208 // event handling is turned off on hide 1209 // when we are using the picker in a field 1210 // therefore onSelect comes AFTER the select 1211 // event. 1212 me.onSelect(); 1213 } 1214 }, 1215 1216 /** 1217 * Perform any post-select actions 1218 * @private 1219 */ 1220 onSelect: function() { 1221 if (this.hideOnSelect) { 1222 this.hide(); 1223 } 1224 }, 1225 1226 /** 1227 * Sets the current value to today. 1228 * @return {Ext.picker.Date} this 1229 */ 1230 selectToday: function() { 1231 var me = this, 1232 btn = me.todayBtn, 1233 handler = me.handler; 1234 1235 if (btn && !btn.disabled) { 1236 me.setValue(Ext.Date.clearTime(new Date())); 1237 me.fireEvent('select', me, me.value); 1238 if (handler) { 1239 handler.call(me.scope || me, me, me.value); 1240 } 1241 me.onSelect(); 1242 } 1243 return me; 1244 }, 1245 1246 /** 1247 * Update the selected cell 1248 * @private 1249 * @param {Date} date The new date 1250 */ 1251 selectedUpdate: function(date) { 1252 var me = this, 1253 t = date.getTime(), 1254 cells = me.cells, 1255 cls = me.selectedCls, 1256 c, 1257 cLen = cells.getCount(), 1258 cell; 1259 1260 me.eventEl.dom.setAttribute('aria-busy', 'true'); 1261 1262 cell = me.activeCell; 1263 1264 if (cell) { 1265 Ext.fly(cell).removeCls(cls); 1266 cell.setAttribute('aria-selected', false); 1267 } 1268 1269 for (c = 0; c < cLen; c++) { 1270 cell = cells.item(c); 1271 1272 if (me.textNodes[c].dateValue === t) { 1273 me.activeCell = cell.dom; 1274 me.eventEl.dom.setAttribute('aria-activedescendant', cell.dom.id); 1275 cell.dom.setAttribute('aria-selected', true); 1276 cell.addCls(cls); 1277 me.fireEvent('highlightitem', me, cell); 1278 break; 1279 } 1280 } 1281 1282 me.eventEl.dom.removeAttribute('aria-busy'); 1283 }, 1284 1285 /** 1286 * Update the contents of the picker for a new month 1287 * @private 1288 * @param {Date} date The new date 1289 */ 1290 fullUpdate: function(date) { 1291 var me = this, 1292 cells = me.cells.elements, 1293 textNodes = me.textNodes, 1294 disabledCls = me.disabledCellCls, 1295 eDate = Ext.Date, 1296 i = 0, 1297 extraDays = 0, 1298 newDate = +eDate.clearTime(date, true), 1299 today = +eDate.clearTime(new Date()), 1300 min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY, 1301 max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY, 1302 ddMatch = me.disabledDatesRE, 1303 ddText = me.disabledDatesText, 1304 ddays = me.disabledDays ? me.disabledDays.join('') : false, 1305 ddaysText = me.disabledDaysText, 1306 format = me.format, 1307 days = eDate.getDaysInMonth(date), 1308 firstOfMonth = eDate.getFirstDateOfMonth(date), 1309 startingPos = firstOfMonth.getDay() - me.startDay, 1310 previousMonth = eDate.add(date, eDate.MONTH, -1), 1311 ariaTitleDateFormat = me.ariaTitleDateFormat, 1312 prevStart, current, disableToday, tempDate, setCellClass, html, cls, 1313 formatValue, value; 1314 1315 if (startingPos < 0) { 1316 startingPos += 7; 1317 } 1318 1319 days += startingPos; 1320 prevStart = eDate.getDaysInMonth(previousMonth) - startingPos; 1321 current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour); 1322 1323 if (me.showToday) { 1324 tempDate = eDate.clearTime(new Date()); 1325 disableToday = (tempDate < min || tempDate > max || 1326 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) || 1327 (ddays && ddays.indexOf(tempDate.getDay()) !== -1)); 1328 1329 if (!me.disabled) { 1330 me.todayBtn.setDisabled(disableToday); 1331 } 1332 } 1333 1334 setCellClass = function(cellIndex, cls){ 1335 var cell = cells[cellIndex], 1336 describedBy = []; 1337 1338 // Cells are not rendered with ids 1339 if (!cell.hasAttribute('id')) { 1340 cell.setAttribute('id', me.id + '-cell-' + cellIndex); 1341 } 1342 1343 // store dateValue number as an expando 1344 value = +eDate.clearTime(current, true); 1345 cell.firstChild.dateValue = value; 1346 1347 cell.setAttribute('aria-label', eDate.format(current, ariaTitleDateFormat)); 1348 1349 // Here and below we can't use title attribute instead of data-qtip 1350 // because JAWS will announce title value before cell content 1351 // which is not what we need. Also we are using aria-describedby attribute 1352 // and not placing the text in aria-label because some cells may have 1353 // compound descriptions (like Today and Disabled day). 1354 cell.removeAttribute('aria-describedby'); 1355 cell.removeAttribute('data-qtip'); 1356 1357 if (value === today) { 1358 cls += ' ' + me.todayCls; 1359 describedBy.push(me.id + '-todayText'); 1360 } 1361 1362 if (value === newDate) { 1363 me.activeCell = cell; 1364 me.eventEl.dom.setAttribute('aria-activedescendant', cell.id); 1365 cell.setAttribute('aria-selected', true); 1366 cls += ' ' + me.selectedCls; 1367 me.fireEvent('highlightitem', me, cell); 1368 } 1369 else { 1370 cell.setAttribute('aria-selected', false); 1371 } 1372 1373 if (value < min) { 1374 cls += ' ' + disabledCls; 1375 describedBy.push(me.id + '-ariaMinText'); 1376 cell.setAttribute('data-qtip', me.minText); 1377 } 1378 else if (value > max) { 1379 cls += ' ' + disabledCls; 1380 describedBy.push(me.id + '-ariaMaxText'); 1381 cell.setAttribute('data-qtip', me.maxText); 1382 } 1383 else if (ddays && ddays.indexOf(current.getDay()) !== -1){ 1384 cell.setAttribute('data-qtip', ddaysText); 1385 describedBy.push(me.id + '-ariaDisabledDaysText'); 1386 cls += ' ' + disabledCls; 1387 } 1388 else if (ddMatch && format){ 1389 formatValue = eDate.dateFormat(current, format); 1390 if(ddMatch.test(formatValue)){ 1391 cell.setAttribute('data-qtip', ddText.replace('%0', formatValue)); 1392 describedBy.push(me.id + '-ariaDisabledDatesText'); 1393 cls += ' ' + disabledCls; 1394 } 1395 } 1396 1397 if (describedBy.length) { 1398 cell.setAttribute('aria-describedby', describedBy.join(' ')); 1399 } 1400 1401 cell.className = cls + ' ' + me.cellCls; 1402 }; 1403 1404 me.eventEl.dom.setAttribute('aria-busy', 'true'); 1405 1406 for (; i < me.numDays; ++i) { 1407 if (i < startingPos) { 1408 html = (++prevStart); 1409 cls = me.prevCls; 1410 } else if (i >= days) { 1411 html = (++extraDays); 1412 cls = me.nextCls; 1413 } else { 1414 html = i - startingPos + 1; 1415 cls = me.activeCls; 1416 } 1417 textNodes[i].innerHTML = html; 1418 current.setDate(current.getDate() + 1); 1419 setCellClass(i, cls); 1420 } 1421 1422 me.eventEl.dom.removeAttribute('aria-busy'); 1423 1424 me.monthBtn.setText(Ext.Date.format(date, me.monthYearFormat)); 1425 }, 1426 1427 /** 1428 * Update the contents of the picker 1429 * @private 1430 * @param {Date} date The new date 1431 * @param {Boolean} forceRefresh True to force a full refresh 1432 */ 1433 update: function(date, forceRefresh) { 1434 var me = this, 1435 active = me.activeDate; 1436 //添加时间相关 1437 date.setHours(me.hour.getValue()); 1438 date.setMinutes(me.minute.getValue()); 1439 date.setSeconds(me.second.getValue()); 1440 1441 if (me.rendered) { 1442 me.activeDate = date; 1443 if (!forceRefresh && active && me.el && 1444 active.getMonth() === date.getMonth() && 1445 active.getFullYear() === date.getFullYear()) { 1446 me.selectedUpdate(date, active); 1447 } else { 1448 me.fullUpdate(date, active); 1449 } 1450 } 1451 return me; 1452 }, 1453 /** 1454 * 确认 按钮触发的调用 1455 */ 1456 okQueDingHandler : function(){ 1457 var me = this, 1458 btn = me.okQueDingBtn; 1459 1460 if(btn && !btn.disabled){ 1461 me.setValue(this.getValue()); 1462 me.fireEvent('select', me, me.value); 1463 me.onSelect(); 1464 } 1465 return me; 1466 }, 1467 /** 1468 * @private 1469 * @inheritdoc 1470 */ 1471 beforeDestroy: function() { 1472 var me = this; 1473 1474 if (me.rendered) { 1475 Ext.destroy( 1476 me.keyNav, 1477 me.monthPicker, 1478 me.monthBtn, 1479 me.nextRepeater, 1480 me.prevRepeater, 1481 me.todayBtn, 1482 me.okQueDingBtn, 1483 me.todayElSpan 1484 ); 1485 delete me.textNodes; 1486 delete me.cells.elements; 1487 } 1488 me.callParent(); 1489 }, 1490 1491 privates: { 1492 // Do the job of a container layout at this point even though we are not a Container. 1493 // TODO: Refactor as a Container. 1494 finishRenderChildren: function () { 1495 var me = this; 1496 1497 me.callParent(); 1498 me.monthBtn.finishRender(); 1499 me.okQueDingBtn.finishRender(); 1500 if (me.showToday) { 1501 me.todayBtn.finishRender(); 1502 } 1503 //添加时间相关 1504 this.hour.finishRender(); 1505 this.minute.finishRender(); 1506 this.second.finishRender(); 1507 }, 1508 1509 getFocusEl: function() { 1510 return this.eventEl; 1511 }, 1512 1513 /** 1514 * Set the disabled state of various internal components 1515 * @param {Boolean} disabled 1516 * @private 1517 */ 1518 syncDisabled: function (disabled) { 1519 var me = this, 1520 keyNav = me.keyNav; 1521 1522 // If we have one, we have all 1523 if (keyNav) { 1524 keyNav.setDisabled(disabled); 1525 me.prevRepeater.setDisabled(disabled); 1526 me.nextRepeater.setDisabled(disabled); 1527 if (me.todayBtn) { 1528 me.todayBtn.setDisabled(disabled); 1529 } 1530 } 1531 } 1532 } 1533 });
11、下面则是扩展出来的一个DeteTimeField控件
完整的DeteTimeField.js代码
1 /** 2 * @docauthor Jason Johnston <jason@sencha.com> 3 * 4 * Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date 5 * validation. 6 * 7 * This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition, 8 * it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats} 9 * configs. These may be reconfigured to use date formats appropriate for the user's locale. 10 * 11 * The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue}, 12 * {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both 13 * in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected. 14 * 15 * # Example usage 16 * 17 * @example 18 * Ext.create('Ext.form.Panel', { 19 * renderTo: Ext.getBody(), 20 * width: 300, 21 * bodyPadding: 10, 22 * title: 'Dates', 23 * items: [{ 24 * xtype: 'datefield', 25 * anchor: '100%', 26 * fieldLabel: 'From', 27 * name: 'from_date', 28 * maxValue: new Date() // limited to the current date or prior 29 * }, { 30 * xtype: 'datefield', 31 * anchor: '100%', 32 * fieldLabel: 'To', 33 * name: 'to_date', 34 * value: new Date() // defaults to today 35 * }] 36 * }); 37 * 38 * # Date Formats Examples 39 * 40 * This example shows a couple of different date format parsing scenarios. Both use custom date format 41 * configurations; the first one matches the configured `format` while the second matches the `altFormats`. 42 * 43 * @example 44 * Ext.create('Ext.form.Panel', { 45 * renderTo: Ext.getBody(), 46 * width: 300, 47 * bodyPadding: 10, 48 * title: 'Dates', 49 * items: [{ 50 * xtype: 'datefield', 51 * anchor: '100%', 52 * fieldLabel: 'Date', 53 * name: 'date', 54 * // The value matches the format; will be parsed and displayed using that format. 55 * format: 'm d Y', 56 * value: '2 4 1978' 57 * }, { 58 * xtype: 'datefield', 59 * anchor: '100%', 60 * fieldLabel: 'Date', 61 * name: 'date', 62 * // The value does not match the format, but does match an altFormat; will be parsed 63 * // using the altFormat and displayed using the format. 64 * format: 'm d Y', 65 * altFormats: 'm,d,Y|m.d.Y', 66 * value: '2.4.1978' 67 * }] 68 * }); 69 */ 70 Ext.define('Ext.ux.DateTimeField', { 71 extend:'Ext.form.field.Picker', 72 alias: 'widget.datetimefield', 73 requires: ['Ext.ux.DateTimePicker'], 74 75 //<locale> 76 /** 77 * @cfg {String} format 78 * The default date format string which can be overriden for localization support. The format must be valid 79 * according to {@link Ext.Date#parse}. 80 */ 81 format : "m/d/Y", 82 //</locale> 83 //<locale> 84 /** 85 * @cfg {String} altFormats 86 * Multiple date formats separated by "|" to try when parsing a user input value and it does not match the defined 87 * format. 88 */ 89 altFormats : "m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d|n-j|n/j", 90 //</locale> 91 //<locale> 92 /** 93 * @cfg {String} disabledDaysText 94 * The tooltip to display when the date falls on a disabled day. 95 */ 96 disabledDaysText : "Disabled", 97 //</locale> 98 //<locale> 99 /** 100 * @cfg {String} disabledDatesText 101 * The tooltip text to display when the date falls on a disabled date. 102 */ 103 disabledDatesText : "Disabled", 104 //</locale> 105 //<locale> 106 /** 107 * @cfg {String} minText 108 * The error text to display when the date in the cell is before {@link #minValue}. 109 */ 110 minText : "The date in this field must be equal to or after {0}", 111 //</locale> 112 //<locale> 113 /** 114 * @cfg {String} maxText 115 * The error text to display when the date in the cell is after {@link #maxValue}. 116 */ 117 maxText : "The date in this field must be equal to or before {0}", 118 //</locale> 119 //<locale> 120 /** 121 * @cfg {String} invalidText 122 * The error text to display when the date in the field is invalid. 123 */ 124 invalidText : "{0} is not a valid date - it must be in the format {1}", 125 //</locale> 126 /** 127 * @cfg {String} [triggerCls='x-form-date-trigger'] 128 * An additional CSS class used to style the trigger button. The trigger will always get the class 'x-form-trigger' 129 * and triggerCls will be **appended** if specified (default class displays a calendar icon). 130 */ 131 triggerCls : Ext.baseCSSPrefix + 'form-date-trigger', 132 /** 133 * @cfg {Boolean} showToday 134 * false to hide the footer area of the Date picker containing the Today button and disable the keyboard handler for 135 * spacebar that selects the current date. 136 */ 137 showToday : true, 138 /** 139 * @cfg {Date/String} minValue 140 * The minimum allowed date. Can be either a Javascript date object or a string date in a valid format. 141 */ 142 /** 143 * @cfg {Date/String} maxValue 144 * The maximum allowed date. Can be either a Javascript date object or a string date in a valid format. 145 */ 146 /** 147 * @cfg {Number[]} disabledDays 148 * An array of days to disable, 0 based. Some examples: 149 * 150 * // disable Sunday and Saturday: 151 * disabledDays: [0, 6] 152 * // disable weekdays: 153 * disabledDays: [1,2,3,4,5] 154 */ 155 /** 156 * @cfg {String[]} disabledDates 157 * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so 158 * they are very powerful. Some examples: 159 * 160 * // disable these exact dates: 161 * disabledDates: ["03/08/2003", "09/16/2003"] 162 * // disable these days for every year: 163 * disabledDates: ["03/08", "09/16"] 164 * // only match the beginning (useful if you are using short years): 165 * disabledDates: ["^03/08"] 166 * // disable every day in March 2006: 167 * disabledDates: ["03/../2006"] 168 * // disable every day in every March: 169 * disabledDates: ["^03"] 170 * 171 * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order 172 * to support regular expressions, if you are using a {@link #format date format} that has "." in it, you will have 173 * to escape the dot when restricting dates. For example: `["03\\.08\\.03"]`. 174 */ 175 176 /** 177 * @cfg {String} submitFormat 178 * The date format string which will be submitted to the server. The format must be valid according to 179 * {@link Ext.Date#parse}. 180 * 181 * Defaults to {@link #format}. 182 */ 183 184 /** 185 * @cfg {Boolean} useStrict 186 * True to enforce strict date parsing to prevent the default Javascript "date rollover". 187 * Defaults to the useStrict parameter set on Ext.Date 188 * See {@link Ext.Date#parse}. 189 */ 190 useStrict: undefined, 191 192 // in the absence of a time value, a default value of 12 noon will be used 193 // (note: 12 noon was chosen because it steers well clear of all DST timezone changes) 194 initTime: '12', // 24 hour format 195 196 initTimeFormat: 'H', 197 198 matchFieldWidth: false, 199 //<locale> 200 /** 201 * @cfg {Number} [startDay=undefined] 202 * Day index at which the week should begin, 0-based. 203 * 204 * Defaults to `0` (Sunday). 205 */ 206 startDay: 0, 207 //</locale> 208 209 /** 210 * @inheritdoc 211 */ 212 valuePublishEvent: ['select', 'blur'], 213 214 initComponent : function(){ 215 var me = this, 216 isString = Ext.isString, 217 min, max; 218 219 min = me.minValue; 220 max = me.maxValue; 221 if(isString(min)){ 222 me.minValue = me.parseDate(min); 223 } 224 if(isString(max)){ 225 me.maxValue = me.parseDate(max); 226 } 227 me.disabledDatesRE = null; 228 me.initDisabledDays(); 229 230 me.callParent(); 231 }, 232 233 initValue: function() { 234 var me = this, 235 value = me.value; 236 237 // If a String value was supplied, try to convert it to a proper Date 238 if (Ext.isString(value)) { 239 me.value = me.rawToValue(value); 240 } 241 242 me.callParent(); 243 }, 244 245 // private 246 initDisabledDays : function(){ 247 if(this.disabledDates){ 248 var dd = this.disabledDates, 249 len = dd.length - 1, 250 re = "(?:", 251 d, 252 dLen = dd.length, 253 date; 254 255 for (d = 0; d < dLen; d++) { 256 date = dd[d]; 257 258 re += Ext.isDate(date) ? '^' + Ext.String.escapeRegex(date.dateFormat(this.format)) + '$' : date; 259 if (d !== len) { 260 re += '|'; 261 } 262 } 263 264 this.disabledDatesRE = new RegExp(re + ')'); 265 } 266 }, 267 268 /** 269 * Replaces any existing disabled dates with new values and refreshes the Date picker. 270 * @param {String[]} disabledDates An array of date strings (see the {@link #disabledDates} config for details on 271 * supported values) used to disable a pattern of dates. 272 */ 273 setDisabledDates : function(disabledDates){ 274 var me = this, 275 picker = me.picker; 276 277 me.disabledDates = disabledDates; 278 me.initDisabledDays(); 279 if (picker) { 280 picker.setDisabledDates(me.disabledDatesRE); 281 } 282 }, 283 284 /** 285 * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker. 286 * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details on 287 * supported values. 288 */ 289 setDisabledDays : function(disabledDays){ 290 var picker = this.picker; 291 292 this.disabledDays = disabledDays; 293 if (picker) { 294 picker.setDisabledDays(disabledDays); 295 } 296 }, 297 298 /** 299 * Replaces any existing {@link #minValue} with the new value and refreshes the Date picker. 300 * @param {Date} value The minimum date that can be selected 301 */ 302 setMinValue : function(value){ 303 var me = this, 304 picker = me.picker, 305 minValue = (Ext.isString(value) ? me.parseDate(value) : value); 306 307 me.minValue = minValue; 308 if (picker) { 309 picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue)); 310 picker.setMinDate(minValue); 311 } 312 }, 313 314 /** 315 * Replaces any existing {@link #maxValue} with the new value and refreshes the Date picker. 316 * @param {Date} value The maximum date that can be selected 317 */ 318 setMaxValue : function(value){ 319 var me = this, 320 picker = me.picker, 321 maxValue = (Ext.isString(value) ? me.parseDate(value) : value); 322 323 me.maxValue = maxValue; 324 if (picker) { 325 picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue)); 326 picker.setMaxDate(maxValue); 327 } 328 }, 329 330 /** 331 * Runs all of Date's validations and returns an array of any errors. Note that this first runs Text's validations, 332 * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that 333 * the date format is valid, that the chosen date is within the min and max date constraints set, that the date 334 * chosen is not in the disabledDates regex and that the day chosed is not one of the disabledDays. 335 * @param {Object} [value] The value to get errors for (defaults to the current field value) 336 * @return {String[]} All validation errors for this field 337 */ 338 getErrors: function(value) { 339 value = arguments.length > 0 ? value : this.formatDate(this.processRawValue(this.getRawValue())); 340 341 var me = this, 342 format = Ext.String.format, 343 clearTime = Ext.Date.clearTime, 344 errors = me.callParent([value]), 345 disabledDays = me.disabledDays, 346 disabledDatesRE = me.disabledDatesRE, 347 minValue = me.minValue, 348 maxValue = me.maxValue, 349 len = disabledDays ? disabledDays.length : 0, 350 i = 0, 351 svalue, 352 fvalue, 353 day, 354 time; 355 356 357 358 if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid 359 return errors; 360 } 361 362 svalue = value; 363 value = me.parseDate(value); 364 if (!value) { 365 errors.push(format(me.invalidText, svalue, Ext.Date.unescapeFormat(me.format))); 366 return errors; 367 } 368 369 time = value.getTime(); 370 if (minValue && time < clearTime(minValue).getTime()) { 371 errors.push(format(me.minText, me.formatDate(minValue))); 372 } 373 374 if (maxValue && time > clearTime(maxValue).getTime()) { 375 errors.push(format(me.maxText, me.formatDate(maxValue))); 376 } 377 378 if (disabledDays) { 379 day = value.getDay(); 380 381 for(; i < len; i++) { 382 if (day === disabledDays[i]) { 383 errors.push(me.disabledDaysText); 384 break; 385 } 386 } 387 } 388 389 fvalue = me.formatDate(value); 390 if (disabledDatesRE && disabledDatesRE.test(fvalue)) { 391 errors.push(format(me.disabledDatesText, fvalue)); 392 } 393 394 return errors; 395 }, 396 397 rawToValue: function(rawValue) { 398 return this.parseDate(rawValue) || rawValue || null; 399 }, 400 401 valueToRaw: function(value) { 402 return this.formatDate(this.parseDate(value)); 403 }, 404 405 /** 406 * @method setValue 407 * Sets the value of the date field. You can pass a date object or any string that can be parsed into a valid date, 408 * using {@link #format} as the date format, according to the same rules as {@link Ext.Date#parse} (the default 409 * format used is "m/d/Y"). 410 * 411 * Usage: 412 * 413 * //All of these calls set the same date value (May 4, 2006) 414 * 415 * //Pass a date object: 416 * var dt = new Date('5/4/2006'); 417 * dateField.setValue(dt); 418 * 419 * //Pass a date string (default format): 420 * dateField.setValue('05/04/2006'); 421 * 422 * //Pass a date string (custom format): 423 * dateField.format = 'Y-m-d'; 424 * dateField.setValue('2006-05-04'); 425 * 426 * @param {String/Date} date The date or valid date string 427 * @return {Ext.form.field.Date} this 428 */ 429 430 /** 431 * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}. 432 * @param {String} value The value to attempt to parse 433 * @param {String} format A valid date format (see {@link Ext.Date#parse}) 434 * @return {Date} The parsed Date object, or null if the value could not be successfully parsed. 435 */ 436 safeParse : function(value, format) { 437 var me = this, 438 utilDate = Ext.Date, 439 result = null, 440 strict = me.useStrict, 441 parsedDate; 442 443 if (utilDate.formatContainsHourInfo(format)) { 444 // if parse format contains hour information, no DST adjustment is necessary 445 result = utilDate.parse(value, format, strict); 446 } else { 447 // set time to 12 noon, then clear the time 448 parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat, strict); 449 if (parsedDate) { 450 result = utilDate.clearTime(parsedDate); 451 } 452 } 453 return result; 454 }, 455 456 // @private 457 getSubmitValue: function() { 458 var format = this.submitFormat || this.format, 459 value = this.getValue(); 460 461 return value ? Ext.Date.format(value, format) : ''; 462 }, 463 464 /** 465 * @private 466 */ 467 parseDate : function(value) { 468 if(!value || Ext.isDate(value)){ 469 return value; 470 } 471 472 var me = this, 473 val = me.safeParse(value, me.format), 474 altFormats = me.altFormats, 475 altFormatsArray = me.altFormatsArray, 476 i = 0, 477 len; 478 479 if (!val && altFormats) { 480 altFormatsArray = altFormatsArray || altFormats.split('|'); 481 len = altFormatsArray.length; 482 for (; i < len && !val; ++i) { 483 val = me.safeParse(value, altFormatsArray[i]); 484 } 485 } 486 return val; 487 }, 488 489 // private 490 formatDate: function(date){ 491 return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date; 492 }, 493 494 createPicker: function() { 495 var me = this, 496 format = Ext.String.format; 497 498 // Create floating Picker BoundList. It will acquire a floatParent by looking up 499 // its ancestor hierarchy (Pickers use their pickerField property as an upward link) 500 // for a floating component. 501 return new Ext.ux.DateTimePicker({ 502 pickerField: me, 503 floating: true, 504 focusable: false, // Key events are listened from the input field which is never blurred 505 hidden: true, 506 minDate: me.minValue, 507 maxDate: me.maxValue, 508 disabledDatesRE: me.disabledDatesRE, 509 disabledDatesText: me.disabledDatesText, 510 disabledDays: me.disabledDays, 511 disabledDaysText: me.disabledDaysText, 512 format: me.format, 513 showToday: me.showToday, 514 startDay: me.startDay, 515 minText: format(me.minText, me.formatDate(me.minValue)), 516 maxText: format(me.maxText, me.formatDate(me.maxValue)), 517 listeners: { 518 scope: me, 519 select: me.onSelect 520 }, 521 keyNavConfig: { 522 esc: function() { 523 me.collapse(); 524 } 525 } 526 }); 527 }, 528 529 onSelect: function(m, d) { 530 var me = this; 531 532 me.setValue(d); 533 me.fireEvent('select', me, d); 534 me.collapse(); 535 }, 536 537 /** 538 * @private 539 * Sets the Date picker's value to match the current field value when expanding. 540 */ 541 onExpand: function() { 542 var me = this, 543 value = me.getValue() instanceof Date ? me.getValue() : new Date(); 544 me.picker.setValue(value); 545 me.picker.hour.setValue(value.getHours()); 546 me.picker.minute.setValue(value.getMinutes()); 547 me.picker.second.setValue(value.getSeconds()); 548 }, 549 550 // private 551 onBlur: function(e) { 552 var me = this, 553 v = me.rawToValue(me.getRawValue()); 554 555 if (Ext.isDate(v)) { 556 me.setValue(v); 557 } 558 me.callParent([e]); 559 } 560 561 /** 562 * @cfg {Boolean} grow 563 * @private 564 */ 565 /** 566 * @cfg {Number} growMin 567 * @private 568 */ 569 /** 570 * @cfg {Number} growMax 571 * @private 572 */ 573 /** 574 * @method autoSize 575 * @private 576 */ 577 });
PS:写这篇文章也是为了方便以后自己用,也方便一下使用EXTJS的同道,为了生命远离EXTJS...
参考文章:http://www.cnblogs.com/linxiong945/p/3977445.html
http://toutiao.com/a4669125293/
http://gogo1217.iteye.com/blog/1856265
http://express.ruanko.com/ruanko-express_69/tech-overnight1.html
http://blog.csdn.net/oscar999/article/details/9984679