DateField.js.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. /**
  2. * @class Ext.picker.Date
  3. * @extends Ext.Component
  4. * <p>A date picker. This class is used by the {@link Ext.form.field.Date} field to allow browsing and
  5. * selection of valid dates in a popup next to the field, but may also be used with other components.</p>
  6. * <p>Typically you will need to implement a handler function to be notified when the user chooses a color from the
  7. * picker; you can register the handler using the {@link #select} event, or by implementing the {@link #handler}
  8. * method.</p>
  9. * <p>By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
  10. * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.</p>
  11. * <p>All the string values documented below may be overridden by including an Ext locale file in your page.</p>
  12. * <p>Example usage:</p>
  13. * <pre><code>new Ext.panel.Panel({
  14. title: 'Choose a future date:',
  15. width: 200,
  16. bodyPadding: 10,
  17. renderTo: Ext.getBody(),
  18. items: [{
  19. xtype: 'datepicker',
  20. minDate: new Date(),
  21. handler: function(picker, date) {
  22. // do something with the selected date
  23. }
  24. }]
  25. });</code></pre>
  26. * {@img Ext.picker.Date/Ext.picker.Date.png Ext.picker.Date component}
  27. *
  28. */
  29. Ext.define('Ext.picker.NewDate', {
  30. extend: 'Ext.Container',
  31. requires: [
  32. 'Ext.data.Model',
  33. 'Ext.view.BoundList',
  34. 'Ext.XTemplate',
  35. 'Ext.button.Button',
  36. 'Ext.button.Split',
  37. 'Ext.util.ClickRepeater',
  38. 'Ext.util.KeyNav',
  39. 'Ext.EventObject',
  40. 'Ext.fx.Manager',
  41. 'Ext.picker.Month'
  42. ],
  43. alias: 'widget.newdatepicker',
  44. alternateClassName: 'Ext.DatePicker',
  45. renderTpl: [
  46. '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
  47. '<div role="presentation" class="{baseCls}-header">',
  48. '<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
  49. '<div class="{baseCls}-month" id="{id}-middleBtnEl"></div>',
  50. '<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
  51. '</div>',
  52. '<div id="{id}-calendarContainer"></div>',
  53. '<tpl if="showToday">',
  54. '<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer"></div>',
  55. '</tpl>',
  56. '</div>',
  57. {
  58. longDay: function(value){
  59. return Ext.Date.format(value, this.longDayFormat);
  60. }
  61. }
  62. ],
  63. calendarTpl: [
  64. '<table class="{baseCls}-inner" cellspacing="0" role="presentation">' +
  65. '<thead role="presentation"><tr role="presentation">' +
  66. '<tpl for="dayNames">' +
  67. '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>' +
  68. '</tpl>' +
  69. '</tr></thead>' +
  70. '<tbody role="presentation"><tr role="presentation">' +
  71. '<tpl for="days">' +
  72. '{#:this.isEndOfWeek}' +
  73. '<td role="gridcell" id="{[Ext.id()]}" class="{[this.getCellClass(values, parent)]}" title="{date:this.titleFormat}">' +
  74. '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">' +
  75. '<em role="presentation"><span role="presentation">{.:date("j")</span></em>' +
  76. '</a>' +
  77. '</td>' +
  78. '</tpl>' +
  79. '</tr></tbody>' +
  80. '</table>', {
  81. cellClass: function(value, data) {
  82. },
  83. titleFormat: function(value) {
  84. return Ext.date.format(value, this.longDayFormat);
  85. },
  86. firstInitial: function(value) {
  87. return value.substr(0,1);
  88. },
  89. isEndOfWeek: function(value) {
  90. // convert from 1 based index to 0 based
  91. // by decrementing value once.
  92. value--;
  93. var end = value % 7 === 0 && value !== 0;
  94. return end ? '</tr><tr role="row">' : '';
  95. }
  96. }],
  97. ariaTitle: 'Date Picker',
  98. /**
  99. * @cfg {String} todayText
  100. * The text to display on the button that selects the current date (defaults to <code>'Today'</code>)
  101. */
  102. todayText : 'Today',
  103. /**
  104. * @cfg {Function} handler
  105. * Optional. A function that will handle the select event of this picker.
  106. * The handler is passed the following parameters:<div class="mdetail-params"><ul>
  107. * <li><code>picker</code> : Ext.picker.Date <div class="sub-desc">This Date picker.</div></li>
  108. * <li><code>date</code> : Date <div class="sub-desc">The selected date.</div></li>
  109. * </ul></div>
  110. */
  111. /**
  112. * @cfg {Object} scope
  113. * The scope (<code><b>this</b></code> reference) in which the <code>{@link #handler}</code>
  114. * function will be called. Defaults to this DatePicker instance.
  115. */
  116. /**
  117. * @cfg {String} todayTip
  118. * A string used to format the message for displaying in a tooltip over the button that
  119. * selects the current date. Defaults to <code>'{0} (Spacebar)'</code> where
  120. * the <code>{0}</code> token is replaced by today's date.
  121. */
  122. todayTip : '{0} (Spacebar)',
  123. /**
  124. * @cfg {String} minText
  125. * The error text to display if the minDate validation fails (defaults to <code>'This date is before the minimum date'</code>)
  126. */
  127. minText : 'This date is before the minimum date',
  128. /**
  129. * @cfg {String} maxText
  130. * The error text to display if the maxDate validation fails (defaults to <code>'This date is after the maximum date'</code>)
  131. */
  132. maxText : 'This date is after the maximum date',
  133. /**
  134. * @cfg {String} format
  135. * The default date format string which can be overriden for localization support. The format must be
  136. * valid according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
  137. */
  138. /**
  139. * @cfg {String} disabledDaysText
  140. * The tooltip to display when the date falls on a disabled day (defaults to <code>'Disabled'</code>)
  141. */
  142. disabledDaysText : 'Disabled',
  143. /**
  144. * @cfg {String} disabledDatesText
  145. * The tooltip text to display when the date falls on a disabled date (defaults to <code>'Disabled'</code>)
  146. */
  147. disabledDatesText : 'Disabled',
  148. /**
  149. * @cfg {Array} monthNames
  150. * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
  151. */
  152. /**
  153. * @cfg {Array} dayNames
  154. * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
  155. */
  156. /**
  157. * @cfg {String} nextText
  158. * The next month navigation button tooltip (defaults to <code>'Next Month (Control+Right)'</code>)
  159. */
  160. nextText : 'Next Month (Control+Right)',
  161. /**
  162. * @cfg {String} prevText
  163. * The previous month navigation button tooltip (defaults to <code>'Previous Month (Control+Left)'</code>)
  164. */
  165. prevText : 'Previous Month (Control+Left)',
  166. /**
  167. * @cfg {String} monthYearText
  168. * The header month selector tooltip (defaults to <code>'Choose a month (Control+Up/Down to move years)'</code>)
  169. */
  170. monthYearText : 'Choose a month (Control+Up/Down to move years)',
  171. /**
  172. * @cfg {Number} startDay
  173. * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
  174. */
  175. startDay : 0,
  176. /**
  177. * @cfg {Boolean} showToday
  178. * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
  179. * that selects the current date (defaults to <code>true</code>).
  180. */
  181. showToday : true,
  182. /**
  183. * @cfg {Date} minDate
  184. * Minimum allowable date (JavaScript date object, defaults to null)
  185. */
  186. /**
  187. * @cfg {Date} maxDate
  188. * Maximum allowable date (JavaScript date object, defaults to null)
  189. */
  190. /**
  191. * @cfg {Array} disabledDays
  192. * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
  193. */
  194. /**
  195. * @cfg {RegExp} disabledDatesRE
  196. * JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates}
  197. * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
  198. * disabledDates value.
  199. */
  200. /**
  201. * @cfg {Array} disabledDates
  202. * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
  203. * expression so they are very powerful. Some examples:
  204. * <ul>
  205. * <li>['03/08/2003', '09/16/2003'] would disable those exact dates</li>
  206. * <li>['03/08', '09/16'] would disable those days for every year</li>
  207. * <li>['^03/08'] would only match the beginning (useful if you are using short years)</li>
  208. * <li>['03/../2006'] would disable every day in March 2006</li>
  209. * <li>['^03'] would disable every day in every March</li>
  210. * </ul>
  211. * Note that the format of the dates included in the array should exactly match the {@link #format} config.
  212. * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
  213. * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
  214. */
  215. /**
  216. * @cfg {Boolean} disableAnim True to disable animations when showing the month picker. Defaults to <tt>false</tt>.
  217. */
  218. disableAnim: false,
  219. /**
  220. * @cfg {String} baseCls
  221. * The base CSS class to apply to this components element (defaults to <tt>'x-datepicker'</tt>).
  222. */
  223. baseCls: Ext.baseCSSPrefix + 'datepicker',
  224. /**
  225. * @cfg {String} selectedCls
  226. * The class to apply to the selected cell. Defaults to <tt>'x-datepicker-selected'</tt>
  227. */
  228. /**
  229. * @cfg {String} disabledCellCls
  230. * The class to apply to disabled cells. Defaults to <tt>'x-datepicker-disabled'</tt>
  231. */
  232. /**
  233. * @cfg {String} longDayFormat
  234. * The format for displaying a date in a longer format. Defaults to <tt>'F d, Y'</tt>
  235. */
  236. longDayFormat: 'F d, Y',
  237. /**
  238. * @cfg {Object} keyNavConfig Specifies optional custom key event handlers for the {@link Ext.util.KeyNav}
  239. * attached to this date picker. Must conform to the config format recognized by the {@link Ext.util.KeyNav}
  240. * constructor. Handlers specified in this object will replace default handlers of the same name.
  241. */
  242. /**
  243. * @cfg {Boolean} focusOnShow
  244. * True to automatically focus the picker on show. Defaults to <tt>false</tt>.
  245. */
  246. focusOnShow: false,
  247. // private
  248. // Set by other components to stop the picker focus being updated when the value changes.
  249. focusOnSelect: true,
  250. width: 178,
  251. // default value used to initialise each date in the DatePicker
  252. // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
  253. initHour: 12, // 24-hour format
  254. numDays: 42,
  255. // private, inherit docs
  256. initComponent : function() {
  257. var me = this,
  258. clearTime = Ext.Date.clearTime;
  259. me.selectedCls = me.baseCls + '-selected';
  260. me.disabledCellCls = me.baseCls + '-disabled';
  261. me.prevCls = me.baseCls + '-prevday';
  262. me.activeCls = me.baseCls + '-active';
  263. me.nextCls = me.baseCls + '-prevday';
  264. me.todayCls = me.baseCls + '-today';
  265. me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
  266. this.callParent();
  267. me.value = me.value ?
  268. clearTime(me.value, true) : clearTime(new Date());
  269. me.addEvents(
  270. /**
  271. * @event select
  272. * Fires when a date is selected
  273. * @param {DatePicker} this DatePicker
  274. * @param {Date} date The selected date
  275. */
  276. 'select'
  277. );
  278. me.initDisabledDays();
  279. },
  280. // private, inherit docs
  281. onRender : function(container, position){
  282. /*
  283. * days array for looping through 6 full weeks (6 weeks * 7 days)
  284. * Note that we explicitly force the size here so the template creates
  285. * all the appropriate cells.
  286. */
  287. var me = this,
  288. days = new Array(me.numDays),
  289. today = Ext.Date.format(new Date(), me.format),
  290. calendarTpl = Ext.create('Ext.XTemplate', me.calendarTpl);
  291. Ext.apply(me.renderData, {
  292. dayNames: me.dayNames,
  293. ariaTitle: me.ariaTitle,
  294. value: me.value,
  295. showToday: me.showToday,
  296. prevText: me.prevText,
  297. nextText: me.nextText,
  298. days: days
  299. });
  300. me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
  301. me.addChildEls('calendarContainer', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl');
  302. this.callParent(arguments);
  303. me.el.unselectable();
  304. // The calendar is a BoundList with aen empty Store.
  305. // collectData returns the fields.
  306. calendarTpl.longDayFormat = me.longDayFormat;
  307. me.calendarView = Ext.create('Ext.view.BoundList', {
  308. store: new Ext.data.Store({fields:[]}),
  309. collectData: function() {
  310. return me.collectData();
  311. },
  312. renderTo: me.calendarContainer,
  313. tpl: calendarTpl,
  314. itemSelector: 'td',
  315. selectedItemCls: me.baseCls + '-selected'
  316. });
  317. me.monthBtn = Ext.create('Ext.button.Split', {
  318. ownerCt: me,
  319. text: '',
  320. tooltip: me.monthYearText,
  321. renderTo: me.middleBtnEl
  322. });
  323. //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
  324. me.todayBtn = Ext.create('Ext.button.Button', {
  325. renderTo: me.footerEl,
  326. text: Ext.String.format(me.todayText, today),
  327. tooltip: Ext.String.format(me.todayTip, today),
  328. handler: me.selectToday,
  329. scope: me
  330. });
  331. },
  332. // Collect a data object for use by the calendar View
  333. collectData : function() {
  334. var me = this;
  335. return {
  336. today: new Date(),
  337. daynames: me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay)),
  338. days: Ext.AbstractView.prototype.apply(me, arguments)
  339. };
  340. },
  341. // private, inherit docs
  342. initEvents: function(){
  343. var me = this;
  344. this.callParent();
  345. me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
  346. handler: me.showPrevMonth,
  347. scope: me,
  348. preventDefault: true,
  349. stopDefault: true
  350. });
  351. me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
  352. handler: me.showNextMonth,
  353. scope: me,
  354. preventDefault:true,
  355. stopDefault:true
  356. });
  357. me.keyNav = Ext.create('Ext.util.KeyNav', me.calendarView.el, Ext.apply({
  358. scope: me,
  359. 'left' : function(e){
  360. if(e.ctrlKey){
  361. me.showPrevMonth();
  362. }
  363. },
  364. 'right' : function(e){
  365. if(e.ctrlKey){
  366. me.showNextMonth();
  367. }
  368. },
  369. 'up' : function(e){
  370. if(e.ctrlKey){
  371. me.showNextYear();
  372. }
  373. },
  374. 'down' : function(e){
  375. if(e.ctrlKey){
  376. me.showPrevYear();
  377. }
  378. },
  379. 'pageUp' : me.showNextMonth,
  380. 'pageDown' : me.showPrevMonth,
  381. 'enter' : function(e){
  382. e.stopPropagation();
  383. return true;
  384. }
  385. }, me.keyNavConfig));
  386. if(me.showToday){
  387. me.todayKeyListener = me.calendarView.el.addKeyListener(Ext.EventObject.SPACE, me.selectToday, me);
  388. }
  389. me.mon(me.calendarView.el, 'mousewheel', me.handleMouseWheel, me);
  390. me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
  391. me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
  392. me.update(me.value);
  393. },
  394. /**
  395. * Setup the disabled dates regex based on config options
  396. * @private
  397. */
  398. initDisabledDays : function(){
  399. var me = this,
  400. dd = me.disabledDates,
  401. re = '(?:',
  402. len;
  403. if(!me.disabledDatesRE && dd){
  404. len = dd.length - 1;
  405. Ext.each(dd, function(d, i){
  406. re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
  407. if(i != len){
  408. re += '|';
  409. }
  410. }, me);
  411. me.disabledDatesRE = new RegExp(re + ')');
  412. }
  413. },
  414. /**
  415. * Replaces any existing disabled dates with new values and refreshes the DatePicker.
  416. * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config
  417. * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
  418. * @return {Ext.picker.Date} this
  419. */
  420. setDisabledDates : function(dd){
  421. var me = this;
  422. if(Ext.isArray(dd)){
  423. me.disabledDates = dd;
  424. me.disabledDatesRE = null;
  425. }else{
  426. me.disabledDatesRE = dd;
  427. }
  428. me.initDisabledDays();
  429. me.update(me.value, true);
  430. return me;
  431. },
  432. /**
  433. * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
  434. * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config
  435. * for details on supported values.
  436. * @return {Ext.picker.Date} this
  437. */
  438. setDisabledDays : function(dd){
  439. this.disabledDays = dd;
  440. return this.update(this.value, true);
  441. },
  442. /**
  443. * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
  444. * @param {Date} value The minimum date that can be selected
  445. * @return {Ext.picker.Date} this
  446. */
  447. setMinDate : function(dt){
  448. this.minDate = dt;
  449. return this.update(this.value, true);
  450. },
  451. /**
  452. * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
  453. * @param {Date} value The maximum date that can be selected
  454. * @return {Ext.picker.Date} this
  455. */
  456. setMaxDate : function(dt){
  457. this.maxDate = dt;
  458. return this.update(this.value, true);
  459. },
  460. /**
  461. * Sets the value of the date field
  462. * @param {Date} value The date to set
  463. * @return {Ext.picker.Date} this
  464. */
  465. setValue : function(value){
  466. this.value = Ext.Date.clearTime(value, true);
  467. return this.update(this.value);
  468. },
  469. /**
  470. * Gets the current selected value of the date field
  471. * @return {Date} The selected date
  472. */
  473. getValue : function(){
  474. return this.value;
  475. },
  476. // private
  477. focus : function(){
  478. this.update(this.activeDate);
  479. },
  480. // private, inherit docs
  481. onEnable: function(){
  482. this.callParent();
  483. this.setDisabledStatus(false);
  484. this.update(this.activeDate);
  485. },
  486. // private, inherit docs
  487. onDisable : function(){
  488. this.callParent();
  489. this.setDisabledStatus(true);
  490. },
  491. /**
  492. * Set the disabled state of various internal components
  493. * @private
  494. * @param {Boolean} disabled
  495. */
  496. setDisabledStatus : function(disabled){
  497. var me = this;
  498. me.keyNav.setDisabled(disabled);
  499. me.prevRepeater.setDisabled(disabled);
  500. me.nextRepeater.setDisabled(disabled);
  501. if (me.showToday) {
  502. me.todayKeyListener.setDisabled(disabled);
  503. me.todayBtn.setDisabled(disabled);
  504. }
  505. },
  506. /**
  507. * Get the current active date.
  508. * @private
  509. * @return {Date} The active date
  510. */
  511. getActive: function(){
  512. return this.activeDate || this.value;
  513. },
  514. /**
  515. * Run any animation required to hide/show the month picker.
  516. * @private
  517. * @param {Boolean} isHide True if it's a hide operation
  518. */
  519. runAnimation: function(isHide){
  520. var options = {
  521. duration: 200
  522. };
  523. if (isHide) {
  524. this.monthPicker.el.slideOut('t', options);
  525. } else {
  526. this.monthPicker.el.slideIn('t', options);
  527. }
  528. },
  529. /**
  530. * Hides the month picker, if it's visible.
  531. * @return {Ext.picker.Date} this
  532. */
  533. hideMonthPicker : function(){
  534. var me = this,
  535. picker = me.monthPicker;
  536. if (picker) {
  537. if (me.disableAnim) {
  538. picker.hide();
  539. } else {
  540. this.runAnimation(true);
  541. }
  542. }
  543. return me;
  544. },
  545. /**
  546. * Show the month picker
  547. * @return {Ext.picker.Date} this
  548. */
  549. showMonthPicker : function(){
  550. var me = this,
  551. picker;
  552. if (me.rendered && !me.disabled) {
  553. picker = me.createMonthPicker();
  554. picker.setValue(me.getActive());
  555. picker.setSize(me.getSize());
  556. picker.setPosition(-1, -1);
  557. if (me.disableAnim) {
  558. picker.show();
  559. } else {
  560. me.runAnimation(false);
  561. }
  562. }
  563. return me;
  564. },
  565. /**
  566. * Create the month picker instance
  567. * @private
  568. * @return {Ext.picker.Month} picker
  569. */
  570. createMonthPicker: function(){
  571. var me = this,
  572. picker = me.monthPicker;
  573. if (!picker) {
  574. me.monthPicker = picker = Ext.create('Ext.picker.Month', {
  575. renderTo: me.el,
  576. floating: true,
  577. shadow: false,
  578. small: me.showToday === false,
  579. listeners: {
  580. scope: me,
  581. cancelclick: me.onCancelClick,
  582. okclick: me.onOkClick,
  583. yeardblclick: me.onOkClick,
  584. monthdblclick: me.onOkClick
  585. }
  586. });
  587. if (!me.disableAnim) {
  588. // hide the element if we're animating to prevent an initial flicker
  589. picker.el.setStyle('display', 'none');
  590. }
  591. me.on('beforehide', me.hideMonthPicker, me);
  592. }
  593. return picker;
  594. },
  595. /**
  596. * Respond to an ok click on the month picker
  597. * @private
  598. */
  599. onOkClick: function(picker, value){
  600. var me = this,
  601. month = value[0],
  602. year = value[1],
  603. date = new Date(year, month, me.getActive().getDate());
  604. if (date.getMonth() !== month) {
  605. // 'fix' the JS rolling date conversion if needed
  606. date = new Date(year, month, 1).getLastDateOfMonth();
  607. }
  608. me.update(date);
  609. me.hideMonthPicker();
  610. },
  611. /**
  612. * Respond to a cancel click on the month picker
  613. * @private
  614. */
  615. onCancelClick: function(){
  616. this.hideMonthPicker();
  617. },
  618. /**
  619. * Show the previous month.
  620. * @return {Ext.picker.Date} this
  621. */
  622. showPrevMonth : function(e){
  623. return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
  624. },
  625. /**
  626. * Show the next month.
  627. * @return {Ext.picker.Date} this
  628. */
  629. showNextMonth : function(e){
  630. return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
  631. },
  632. /**
  633. * Show the previous year.
  634. * @return {Ext.picker.Date} this
  635. */
  636. showPrevYear : function(){
  637. this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
  638. },
  639. /**
  640. * Show the next year.
  641. * @return {Ext.picker.Date} this
  642. */
  643. showNextYear : function(){
  644. this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
  645. },
  646. /**
  647. * Respond to the mouse wheel event
  648. * @private
  649. * @param {Ext.EventObject} e
  650. */
  651. handleMouseWheel : function(e){
  652. e.stopEvent();
  653. if(!this.disabled){
  654. var delta = e.getWheelDelta();
  655. if(delta > 0){
  656. this.showPrevMonth();
  657. } else if(delta < 0){
  658. this.showNextMonth();
  659. }
  660. }
  661. },
  662. /**
  663. * Respond to a date being clicked in the picker
  664. * @private
  665. * @param {Ext.EventObject} e
  666. * @param {HTMLElement} t
  667. */
  668. handleDateClick : function(e, t){
  669. var me = this,
  670. handler = me.handler;
  671. e.stopEvent();
  672. if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
  673. me.cancelFocus = me.focusOnSelect === false;
  674. me.setValue(new Date(t.dateValue));
  675. delete me.cancelFocus;
  676. me.fireEvent('select', me, me.value);
  677. if (handler) {
  678. handler.call(me.scope || me, me, me.value);
  679. }
  680. // event handling is turned off on hide
  681. // when we are using the picker in a field
  682. // therefore onSelect comes AFTER the select
  683. // event.
  684. me.onSelect();
  685. }
  686. },
  687. /**
  688. * Perform any post-select actions
  689. * @private
  690. */
  691. onSelect: function() {
  692. if (this.hideOnSelect) {
  693. this.hide();
  694. }
  695. },
  696. /**
  697. * Sets the current value to today.
  698. * @return {Ext.picker.Date} this
  699. */
  700. selectToday : function(){
  701. var me = this,
  702. btn = me.todayBtn,
  703. handler = me.handler;
  704. if(btn && !btn.disabled){
  705. me.setValue(Ext.Date.clearTime(new Date()));
  706. me.fireEvent('select', me, me.value);
  707. if (handler) {
  708. handler.call(me.scope || me, me, me.value);
  709. }
  710. me.onSelect();
  711. }
  712. return me;
  713. },
  714. /**
  715. * Update the selected cell
  716. * @private
  717. * @param {Date} date The new date
  718. * @param {Date} active The active date
  719. */
  720. selectedUpdate: function(date, active){
  721. var me = this,
  722. t = date.getTime(),
  723. cells = me.cells,
  724. cls = me.selectedCls;
  725. cells.removeCls(cls);
  726. cells.each(function(c){
  727. if (c.dom.firstChild.dateValue == t) {
  728. me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
  729. c.addCls(cls);
  730. if(me.isVisible() && !me.cancelFocus){
  731. Ext.fly(c.dom.firstChild).focus(50);
  732. }
  733. return false;
  734. }
  735. }, this);
  736. },
  737. /**
  738. * Update the contents of the picker for a new month
  739. * @private
  740. * @param {Date} date The new date
  741. * @param {Date} active The active date
  742. */
  743. fullUpdate: function(date, active){
  744. var me = this,
  745. cells = me.cells.elements,
  746. textNodes = me.textNodes,
  747. disabledCls = me.disabledCellCls,
  748. eDate = Ext.Date,
  749. i = 0,
  750. extraDays = 0,
  751. visible = me.isVisible(),
  752. sel = +eDate.clearTime(date, true),
  753. today = +eDate.clearTime(new Date()),
  754. min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
  755. max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
  756. ddMatch = me.disabledDatesRE,
  757. ddText = me.disabledDatesText,
  758. ddays = me.disabledDays ? me.disabledDays.join('') : false,
  759. ddaysText = me.disabledDaysText,
  760. format = me.format,
  761. days = eDate.getDaysInMonth(date),
  762. firstOfMonth = eDate.getFirstDateOfMonth(date),
  763. startingPos = firstOfMonth.getDay() - me.startDay,
  764. previousMonth = eDate.add(date, eDate.MONTH, -1),
  765. longDayFormat = me.longDayFormat,
  766. prevStart,
  767. current,
  768. disableToday,
  769. tempDate,
  770. setCellClass,
  771. html,
  772. cls,
  773. formatValue,
  774. value;
  775. if (startingPos < 0) {
  776. startingPos += 7;
  777. }
  778. days += startingPos;
  779. prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
  780. current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
  781. if (me.showToday) {
  782. tempDate = eDate.clearTime(new Date());
  783. disableToday = (tempDate < min || tempDate > max ||
  784. (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
  785. (ddays && ddays.indexOf(tempDate.getDay()) != -1));
  786. if (!me.disabled) {
  787. me.todayBtn.setDisabled(disableToday);
  788. me.todayKeyListener.setDisabled(disableToday);
  789. }
  790. }
  791. setCellClass = function(cell){
  792. value = +eDate.clearTime(current, true);
  793. cell.title = eDate.format(current, longDayFormat);
  794. // store dateValue number as an expando
  795. cell.firstChild.dateValue = value;
  796. if(value == today){
  797. cell.className += ' ' + me.todayCls;
  798. cell.title = me.todayText;
  799. }
  800. if(value == sel){
  801. cell.className += ' ' + me.selectedCls;
  802. me.el.dom.setAttribute('aria-activedescendant', cell.id);
  803. if (visible && me.floating) {
  804. Ext.fly(cell.firstChild).focus(50);
  805. }
  806. }
  807. // disabling
  808. if(value < min) {
  809. cell.className = disabledCls;
  810. cell.title = me.minText;
  811. return;
  812. }
  813. if(value > max) {
  814. cell.className = disabledCls;
  815. cell.title = me.maxText;
  816. return;
  817. }
  818. if(ddays){
  819. if(ddays.indexOf(current.getDay()) != -1){
  820. cell.title = ddaysText;
  821. cell.className = disabledCls;
  822. }
  823. }
  824. if(ddMatch && format){
  825. formatValue = eDate.dateFormat(current, format);
  826. if(ddMatch.test(formatValue)){
  827. cell.title = ddText.replace('%0', formatValue);
  828. cell.className = disabledCls;
  829. }
  830. }
  831. };
  832. for(; i < me.numDays; ++i) {
  833. if (i < startingPos) {
  834. html = (++prevStart);
  835. cls = me.prevCls;
  836. } else if (i >= days) {
  837. html = (++extraDays);
  838. cls = me.nextCls;
  839. } else {
  840. html = i - startingPos + 1;
  841. cls = me.activeCls;
  842. }
  843. textNodes[i].innerHTML = html;
  844. cells[i].className = cls;
  845. current.setDate(current.getDate() + 1);
  846. setCellClass(cells[i]);
  847. }
  848. me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
  849. },
  850. /**
  851. * Update the contents of the picker
  852. * @private
  853. * @param {Date} date The new date
  854. * @param {Boolean} forceRefresh True to force a full refresh
  855. */
  856. update : function(date, forceRefresh){
  857. var me = this,
  858. active = me.activeDate;
  859. if (me.rendered) {
  860. me.activeDate = date;
  861. if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
  862. me.selectedUpdate(date, active);
  863. } else {
  864. me.fullUpdate(date, active);
  865. }
  866. }
  867. return me;
  868. },
  869. // private, inherit docs
  870. beforeDestroy : function() {
  871. var me = this;
  872. if (me.rendered) {
  873. Ext.destroy(
  874. me.todayKeyListener,
  875. me.keyNav,
  876. me.monthPicker,
  877. me.monthBtn,
  878. me.nextRepeater,
  879. me.prevRepeater,
  880. me.todayBtn
  881. );
  882. delete me.textNodes;
  883. delete me.cells.elements;
  884. }
  885. },
  886. // private, inherit docs
  887. onShow: function() {
  888. this.callParent(arguments);
  889. if (this.focusOnShow) {
  890. this.focus();
  891. }
  892. }
  893. },
  894. // After dependencies have loaded:
  895. function() {
  896. var proto = this.prototype;
  897. proto.monthNames = Ext.Date.monthNames;
  898. proto.dayNames = Ext.Date.dayNames;
  899. proto.format = Ext.Date.defaultFormat;
  900. });