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