datepicker.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Date picker implementation.
  16. *
  17. * @author eae@google.com (Emil A Eklund)
  18. * @see ../demos/datepicker.html
  19. */
  20. goog.provide('goog.ui.DatePicker');
  21. goog.provide('goog.ui.DatePicker.Events');
  22. goog.provide('goog.ui.DatePickerEvent');
  23. goog.require('goog.a11y.aria');
  24. goog.require('goog.asserts');
  25. goog.require('goog.date.Date');
  26. goog.require('goog.date.DateRange');
  27. goog.require('goog.date.Interval');
  28. goog.require('goog.dom');
  29. goog.require('goog.dom.NodeType');
  30. goog.require('goog.dom.TagName');
  31. goog.require('goog.dom.classlist');
  32. goog.require('goog.events.Event');
  33. goog.require('goog.events.EventType');
  34. goog.require('goog.events.KeyHandler');
  35. goog.require('goog.i18n.DateTimeFormat');
  36. goog.require('goog.i18n.DateTimePatterns');
  37. goog.require('goog.i18n.DateTimeSymbols');
  38. goog.require('goog.style');
  39. goog.require('goog.ui.Component');
  40. goog.require('goog.ui.DefaultDatePickerRenderer');
  41. goog.require('goog.ui.IdGenerator');
  42. /**
  43. * DatePicker widget. Allows a single date to be selected from a calendar like
  44. * view.
  45. *
  46. * @param {goog.date.Date|Date=} opt_date Date to initialize the date picker
  47. * with, defaults to the current date.
  48. * @param {Object=} opt_dateTimeSymbols Date and time symbols to use.
  49. * Defaults to goog.i18n.DateTimeSymbols if not set.
  50. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
  51. * @param {goog.ui.DatePickerRenderer=} opt_renderer Optional Date picker
  52. * renderer.
  53. * @constructor
  54. * @extends {goog.ui.Component}
  55. */
  56. goog.ui.DatePicker = function(
  57. opt_date, opt_dateTimeSymbols, opt_domHelper, opt_renderer) {
  58. goog.ui.Component.call(this, opt_domHelper);
  59. /**
  60. * Date and time symbols to use.
  61. * @type {!goog.i18n.DateTimeSymbolsType}
  62. * @private
  63. */
  64. this.symbols_ = /** @type {!goog.i18n.DateTimeSymbolsType} */ (
  65. opt_dateTimeSymbols || goog.i18n.DateTimeSymbols);
  66. this.wdayNames_ = this.symbols_.STANDALONESHORTWEEKDAYS;
  67. // Formatters for the various areas of the picker
  68. this.i18nDateFormatterDay_ = new goog.i18n.DateTimeFormat('d', this.symbols_);
  69. this.i18nDateFormatterDay2_ =
  70. new goog.i18n.DateTimeFormat('dd', this.symbols_);
  71. this.i18nDateFormatterWeek_ =
  72. new goog.i18n.DateTimeFormat('w', this.symbols_);
  73. // Formatter for day grid aria label.
  74. this.i18nDateFormatterDayAriaLabel_ =
  75. new goog.i18n.DateTimeFormat('M d', this.symbols_);
  76. // Previous implementation did not use goog.i18n.DateTimePatterns,
  77. // so it is likely most developers did not set it.
  78. // This is why the fallback to a hard-coded string (just in case).
  79. var patYear = goog.i18n.DateTimePatterns.YEAR_FULL || 'y';
  80. this.i18nDateFormatterYear_ =
  81. new goog.i18n.DateTimeFormat(patYear, this.symbols_);
  82. var patMMMMy = goog.i18n.DateTimePatterns.YEAR_MONTH_FULL || 'MMMM y';
  83. this.i18nDateFormatterMonthYear_ =
  84. new goog.i18n.DateTimeFormat(patMMMMy, this.symbols_);
  85. /**
  86. * @type {!goog.ui.DatePickerRenderer}
  87. * @private
  88. */
  89. this.renderer_ = opt_renderer ||
  90. new goog.ui.DefaultDatePickerRenderer(
  91. this.getBaseCssClass(), this.getDomHelper());
  92. /**
  93. * Selected date.
  94. * @type {goog.date.Date}
  95. * @private
  96. */
  97. this.date_ = new goog.date.Date(opt_date);
  98. this.date_.setFirstWeekCutOffDay(this.symbols_.FIRSTWEEKCUTOFFDAY);
  99. this.date_.setFirstDayOfWeek(this.symbols_.FIRSTDAYOFWEEK);
  100. /**
  101. * Active month.
  102. * @type {goog.date.Date}
  103. * @private
  104. */
  105. this.activeMonth_ = this.date_.clone();
  106. this.activeMonth_.setDate(1);
  107. /**
  108. * Class names to apply to the weekday columns.
  109. * @type {Array<string>}
  110. * @private
  111. */
  112. this.wdayStyles_ = ['', '', '', '', '', '', ''];
  113. this.wdayStyles_[this.symbols_.WEEKENDRANGE[0]] =
  114. goog.getCssName(this.getBaseCssClass(), 'wkend-start');
  115. this.wdayStyles_[this.symbols_.WEEKENDRANGE[1]] =
  116. goog.getCssName(this.getBaseCssClass(), 'wkend-end');
  117. /**
  118. * Object that is being used to cache key handlers.
  119. * @type {Object}
  120. * @private
  121. */
  122. this.keyHandlers_ = {};
  123. /**
  124. * Collection of dates that make up the date picker.
  125. * @type {!Array<!Array<!goog.date.Date>>}
  126. * @private
  127. */
  128. this.grid_ = [];
  129. /** @private {Array<!Array<Element>>} */
  130. this.elTable_;
  131. /**
  132. * TODO(tbreisacher): Remove external references to this field,
  133. * and make it private.
  134. * @type {Element}
  135. */
  136. this.tableBody_;
  137. /** @private {Element} */
  138. this.tableFoot_;
  139. /** @private {Element} */
  140. this.elYear_;
  141. /** @private {Element} */
  142. this.elMonth_;
  143. /** @private {Element} */
  144. this.elToday_;
  145. /** @private {Element} */
  146. this.elNone_;
  147. /** @private {Element} */
  148. this.menu_;
  149. /** @private {Element} */
  150. this.menuSelected_;
  151. /** @private {function(Element)} */
  152. this.menuCallback_;
  153. };
  154. goog.inherits(goog.ui.DatePicker, goog.ui.Component);
  155. goog.tagUnsealableClass(goog.ui.DatePicker);
  156. /**
  157. * Flag indicating if the number of weeks shown should be fixed.
  158. * @type {boolean}
  159. * @private
  160. */
  161. goog.ui.DatePicker.prototype.showFixedNumWeeks_ = true;
  162. /**
  163. * Flag indicating if days from other months should be shown.
  164. * @type {boolean}
  165. * @private
  166. */
  167. goog.ui.DatePicker.prototype.showOtherMonths_ = true;
  168. /**
  169. * Range of dates which are selectable by the user.
  170. * @type {!goog.date.DateRange}
  171. * @private
  172. */
  173. goog.ui.DatePicker.prototype.userSelectableDateRange_ =
  174. goog.date.DateRange.allTime();
  175. /**
  176. * Flag indicating if extra week(s) always should be added at the end. If not
  177. * set the extra week is added at the beginning if the number of days shown
  178. * from the previous month is less then the number from the next month.
  179. * @type {boolean}
  180. * @private
  181. */
  182. goog.ui.DatePicker.prototype.extraWeekAtEnd_ = true;
  183. /**
  184. * Flag indicating if week numbers should be shown.
  185. * @type {boolean}
  186. * @private
  187. */
  188. goog.ui.DatePicker.prototype.showWeekNum_ = true;
  189. /**
  190. * Flag indicating if weekday names should be shown.
  191. * @type {boolean}
  192. * @private
  193. */
  194. goog.ui.DatePicker.prototype.showWeekdays_ = true;
  195. /**
  196. * Flag indicating if none is a valid selection. Also controls if the none
  197. * button should be shown or not.
  198. * @type {boolean}
  199. * @private
  200. */
  201. goog.ui.DatePicker.prototype.allowNone_ = true;
  202. /**
  203. * Flag indicating if the today button should be shown.
  204. * @type {boolean}
  205. * @private
  206. */
  207. goog.ui.DatePicker.prototype.showToday_ = true;
  208. /**
  209. * Flag indicating if the picker should use a simple navigation menu that only
  210. * contains controls for navigating to the next and previous month. The default
  211. * navigation menu contains controls for navigating to the next/previous month,
  212. * next/previous year, and menus for jumping to specific months and years.
  213. * @type {boolean}
  214. * @private
  215. */
  216. goog.ui.DatePicker.prototype.simpleNavigation_ = false;
  217. /**
  218. * Custom decorator function. Takes a goog.date.Date object, returns a String
  219. * representing a CSS class or null if no special styling applies
  220. * @type {Function}
  221. * @private
  222. */
  223. goog.ui.DatePicker.prototype.decoratorFunction_ = null;
  224. /**
  225. * Flag indicating if the dates should be printed as a two charater date.
  226. * @type {boolean}
  227. * @private
  228. */
  229. goog.ui.DatePicker.prototype.longDateFormat_ = false;
  230. /**
  231. * Element for navigation row on a datepicker.
  232. * @type {Element}
  233. * @private
  234. */
  235. goog.ui.DatePicker.prototype.elNavRow_ = null;
  236. /**
  237. * Element for the month/year in the navigation row.
  238. * @type {Element}
  239. * @private
  240. */
  241. goog.ui.DatePicker.prototype.elMonthYear_ = null;
  242. /**
  243. * Element for footer row on a datepicker.
  244. * @type {Element}
  245. * @private
  246. */
  247. goog.ui.DatePicker.prototype.elFootRow_ = null;
  248. /**
  249. * Generator for unique table cell IDs.
  250. * @type {goog.ui.IdGenerator}
  251. * @private
  252. */
  253. goog.ui.DatePicker.prototype.cellIdGenerator_ =
  254. goog.ui.IdGenerator.getInstance();
  255. /**
  256. * Name of base CSS class of datepicker.
  257. * @type {string}
  258. * @private
  259. */
  260. goog.ui.DatePicker.BASE_CSS_CLASS_ = goog.getCssName('goog-date-picker');
  261. /**
  262. * The numbers of years to show before and after the current one in the
  263. * year pull-down menu. A total of YEAR_MENU_RANGE * 2 + 1 will be shown.
  264. * Example: for range = 2 and year 2013 => [2011, 2012, 2013, 2014, 2015]
  265. * @const {number}
  266. * @private
  267. */
  268. goog.ui.DatePicker.YEAR_MENU_RANGE_ = 5;
  269. /**
  270. * Constants for event names
  271. *
  272. * @const
  273. */
  274. goog.ui.DatePicker.Events = {
  275. CHANGE: 'change',
  276. CHANGE_ACTIVE_MONTH: 'changeActiveMonth',
  277. SELECT: 'select'
  278. };
  279. /**
  280. * @deprecated Use isInDocument.
  281. */
  282. goog.ui.DatePicker.prototype.isCreated =
  283. goog.ui.DatePicker.prototype.isInDocument;
  284. /**
  285. * @return {number} The first day of week, 0 = Monday, 6 = Sunday.
  286. */
  287. goog.ui.DatePicker.prototype.getFirstWeekday = function() {
  288. return this.activeMonth_.getFirstDayOfWeek();
  289. };
  290. /**
  291. * Returns the class name associated with specified weekday.
  292. * @param {number} wday The week day number to get the class name for.
  293. * @return {string} The class name associated with specified weekday.
  294. */
  295. goog.ui.DatePicker.prototype.getWeekdayClass = function(wday) {
  296. return this.wdayStyles_[wday];
  297. };
  298. /**
  299. * @return {boolean} Whether a fixed number of weeks should be showed. If not
  300. * only weeks for the current month will be shown.
  301. */
  302. goog.ui.DatePicker.prototype.getShowFixedNumWeeks = function() {
  303. return this.showFixedNumWeeks_;
  304. };
  305. /**
  306. * @return {boolean} Whether a days from the previous and/or next month should
  307. * be shown.
  308. */
  309. goog.ui.DatePicker.prototype.getShowOtherMonths = function() {
  310. return this.showOtherMonths_;
  311. };
  312. /**
  313. * @return {boolean} Whether a the extra week(s) added always should be at the
  314. * end. Only applicable if a fixed number of weeks are shown.
  315. */
  316. goog.ui.DatePicker.prototype.getExtraWeekAtEnd = function() {
  317. return this.extraWeekAtEnd_;
  318. };
  319. /**
  320. * @return {boolean} Whether week numbers should be shown.
  321. */
  322. goog.ui.DatePicker.prototype.getShowWeekNum = function() {
  323. return this.showWeekNum_;
  324. };
  325. /**
  326. * @return {boolean} Whether weekday names should be shown.
  327. */
  328. goog.ui.DatePicker.prototype.getShowWeekdayNames = function() {
  329. return this.showWeekdays_;
  330. };
  331. /**
  332. * @return {boolean} Whether none is a valid selection.
  333. */
  334. goog.ui.DatePicker.prototype.getAllowNone = function() {
  335. return this.allowNone_;
  336. };
  337. /**
  338. * @return {boolean} Whether the today button should be shown.
  339. */
  340. goog.ui.DatePicker.prototype.getShowToday = function() {
  341. return this.showToday_;
  342. };
  343. /**
  344. * Returns base CSS class. This getter is used to get base CSS class part.
  345. * All CSS class names in component are created as:
  346. * goog.getCssName(this.getBaseCssClass(), 'CLASS_NAME')
  347. * @return {string} Base CSS class.
  348. */
  349. goog.ui.DatePicker.prototype.getBaseCssClass = function() {
  350. return goog.ui.DatePicker.BASE_CSS_CLASS_;
  351. };
  352. /**
  353. * Sets the first day of week
  354. *
  355. * @param {number} wday Week day, 0 = Monday, 6 = Sunday.
  356. */
  357. goog.ui.DatePicker.prototype.setFirstWeekday = function(wday) {
  358. this.activeMonth_.setFirstDayOfWeek(wday);
  359. this.updateCalendarGrid_();
  360. this.redrawWeekdays_();
  361. };
  362. /**
  363. * Sets class name associated with specified weekday.
  364. *
  365. * @param {number} wday Week day, 0 = Monday, 6 = Sunday.
  366. * @param {string} className Class name.
  367. */
  368. goog.ui.DatePicker.prototype.setWeekdayClass = function(wday, className) {
  369. this.wdayStyles_[wday] = className;
  370. this.redrawCalendarGrid_();
  371. };
  372. /**
  373. * Sets whether a fixed number of weeks should be showed. If not only weeks
  374. * for the current month will be showed.
  375. *
  376. * @param {boolean} b Whether a fixed number of weeks should be showed.
  377. */
  378. goog.ui.DatePicker.prototype.setShowFixedNumWeeks = function(b) {
  379. this.showFixedNumWeeks_ = b;
  380. this.updateCalendarGrid_();
  381. };
  382. /**
  383. * Sets whether a days from the previous and/or next month should be shown.
  384. *
  385. * @param {boolean} b Whether a days from the previous and/or next month should
  386. * be shown.
  387. */
  388. goog.ui.DatePicker.prototype.setShowOtherMonths = function(b) {
  389. this.showOtherMonths_ = b;
  390. this.redrawCalendarGrid_();
  391. };
  392. /**
  393. * Sets the range of dates which may be selected by the user.
  394. *
  395. * @param {!goog.date.DateRange} dateRange The range of selectable dates.
  396. */
  397. goog.ui.DatePicker.prototype.setUserSelectableDateRange = function(dateRange) {
  398. this.userSelectableDateRange_ = dateRange;
  399. };
  400. /**
  401. * Gets the range of dates which may be selected by the user.
  402. *
  403. * @return {!goog.date.DateRange} The range of selectable dates.
  404. */
  405. goog.ui.DatePicker.prototype.getUserSelectableDateRange = function() {
  406. return this.userSelectableDateRange_;
  407. };
  408. /**
  409. * Determine if a date may be selected by the user.
  410. *
  411. * @param {!goog.date.Date} date The date to be tested.
  412. * @return {boolean} Whether the user may select this date.
  413. * @private
  414. */
  415. goog.ui.DatePicker.prototype.isUserSelectableDate_ = function(date) {
  416. return this.userSelectableDateRange_.contains(date);
  417. };
  418. /**
  419. * Sets whether the picker should use a simple navigation menu that only
  420. * contains controls for navigating to the next and previous month. The default
  421. * navigation menu contains controls for navigating to the next/previous month,
  422. * next/previous year, and menus for jumping to specific months and years.
  423. *
  424. * @param {boolean} b Whether to use a simple navigation menu.
  425. */
  426. goog.ui.DatePicker.prototype.setUseSimpleNavigationMenu = function(b) {
  427. this.simpleNavigation_ = b;
  428. this.updateNavigationRow_();
  429. this.updateCalendarGrid_();
  430. };
  431. /**
  432. * Sets whether a the extra week(s) added always should be at the end. Only
  433. * applicable if a fixed number of weeks are shown.
  434. *
  435. * @param {boolean} b Whether a the extra week(s) added always should be at the
  436. * end.
  437. */
  438. goog.ui.DatePicker.prototype.setExtraWeekAtEnd = function(b) {
  439. this.extraWeekAtEnd_ = b;
  440. this.updateCalendarGrid_();
  441. };
  442. /**
  443. * Sets whether week numbers should be shown.
  444. *
  445. * @param {boolean} b Whether week numbers should be shown.
  446. */
  447. goog.ui.DatePicker.prototype.setShowWeekNum = function(b) {
  448. this.showWeekNum_ = b;
  449. // The navigation and footer rows may rely on the number of visible columns,
  450. // so we update them when adding/removing the weeknum column.
  451. this.updateNavigationRow_();
  452. this.updateFooterRow_();
  453. this.updateCalendarGrid_();
  454. };
  455. /**
  456. * Sets whether weekday names should be shown.
  457. *
  458. * @param {boolean} b Whether weekday names should be shown.
  459. */
  460. goog.ui.DatePicker.prototype.setShowWeekdayNames = function(b) {
  461. this.showWeekdays_ = b;
  462. this.redrawWeekdays_();
  463. this.redrawCalendarGrid_();
  464. };
  465. /**
  466. * Sets whether the picker uses narrow weekday names ('M', 'T', 'W', ...).
  467. *
  468. * The default behavior is to use short names ('Mon', 'Tue', 'Wed', ...).
  469. *
  470. * @param {boolean} b Whether to use narrow weekday names.
  471. */
  472. goog.ui.DatePicker.prototype.setUseNarrowWeekdayNames = function(b) {
  473. this.wdayNames_ = b ? this.symbols_.STANDALONENARROWWEEKDAYS :
  474. this.symbols_.STANDALONESHORTWEEKDAYS;
  475. this.redrawWeekdays_();
  476. };
  477. /**
  478. * Sets whether none is a valid selection.
  479. *
  480. * @param {boolean} b Whether none is a valid selection.
  481. */
  482. goog.ui.DatePicker.prototype.setAllowNone = function(b) {
  483. this.allowNone_ = b;
  484. if (this.elNone_) {
  485. this.updateTodayAndNone_();
  486. }
  487. };
  488. /**
  489. * Sets whether the today button should be shown.
  490. *
  491. * @param {boolean} b Whether the today button should be shown.
  492. */
  493. goog.ui.DatePicker.prototype.setShowToday = function(b) {
  494. this.showToday_ = b;
  495. if (this.elToday_) {
  496. this.updateTodayAndNone_();
  497. }
  498. };
  499. /**
  500. * Updates the display style of the None and Today buttons as well as hides the
  501. * table foot if both are hidden.
  502. * @private
  503. */
  504. goog.ui.DatePicker.prototype.updateTodayAndNone_ = function() {
  505. goog.style.setElementShown(this.elToday_, this.showToday_);
  506. goog.style.setElementShown(this.elNone_, this.allowNone_);
  507. goog.style.setElementShown(
  508. this.tableFoot_, this.showToday_ || this.allowNone_);
  509. };
  510. /**
  511. * Sets the decorator function. The function should have the interface of
  512. * {string} f({goog.date.Date});
  513. * and return a String representing a CSS class to decorate the cell
  514. * corresponding to the date specified.
  515. *
  516. * @param {Function} f The decorator function.
  517. */
  518. goog.ui.DatePicker.prototype.setDecorator = function(f) {
  519. this.decoratorFunction_ = f;
  520. };
  521. /**
  522. * Sets whether the date will be printed in long format. In long format, dates
  523. * such as '1' will be printed as '01'.
  524. *
  525. * @param {boolean} b Whethere dates should be printed in long format.
  526. */
  527. goog.ui.DatePicker.prototype.setLongDateFormat = function(b) {
  528. this.longDateFormat_ = b;
  529. this.redrawCalendarGrid_();
  530. };
  531. /**
  532. * Changes the active month to the previous one.
  533. */
  534. goog.ui.DatePicker.prototype.previousMonth = function() {
  535. this.activeMonth_.add(new goog.date.Interval(goog.date.Interval.MONTHS, -1));
  536. this.updateCalendarGrid_();
  537. this.fireChangeActiveMonthEvent_();
  538. };
  539. /**
  540. * Changes the active month to the next one.
  541. */
  542. goog.ui.DatePicker.prototype.nextMonth = function() {
  543. this.activeMonth_.add(new goog.date.Interval(goog.date.Interval.MONTHS, 1));
  544. this.updateCalendarGrid_();
  545. this.fireChangeActiveMonthEvent_();
  546. };
  547. /**
  548. * Changes the active year to the previous one.
  549. */
  550. goog.ui.DatePicker.prototype.previousYear = function() {
  551. this.activeMonth_.add(new goog.date.Interval(goog.date.Interval.YEARS, -1));
  552. this.updateCalendarGrid_();
  553. this.fireChangeActiveMonthEvent_();
  554. };
  555. /**
  556. * Changes the active year to the next one.
  557. */
  558. goog.ui.DatePicker.prototype.nextYear = function() {
  559. this.activeMonth_.add(new goog.date.Interval(goog.date.Interval.YEARS, 1));
  560. this.updateCalendarGrid_();
  561. this.fireChangeActiveMonthEvent_();
  562. };
  563. /**
  564. * Selects the current date.
  565. */
  566. goog.ui.DatePicker.prototype.selectToday = function() {
  567. this.setDate(new goog.date.Date());
  568. };
  569. /**
  570. * Clears the selection.
  571. */
  572. goog.ui.DatePicker.prototype.selectNone = function() {
  573. if (this.allowNone_) {
  574. this.setDate(null);
  575. }
  576. };
  577. /**
  578. * @return {!goog.date.Date} The active month displayed.
  579. */
  580. goog.ui.DatePicker.prototype.getActiveMonth = function() {
  581. return this.activeMonth_.clone();
  582. };
  583. /**
  584. * @return {goog.date.Date} The selected date or null if nothing is selected.
  585. */
  586. goog.ui.DatePicker.prototype.getDate = function() {
  587. return this.date_ && this.date_.clone();
  588. };
  589. /**
  590. * @param {number} row The row in the grid.
  591. * @param {number} col The column in the grid.
  592. * @return {goog.date.Date} The date in the grid or null if there is none.
  593. */
  594. goog.ui.DatePicker.prototype.getDateAt = function(row, col) {
  595. return this.grid_[row] ?
  596. this.grid_[row][col] ? this.grid_[row][col].clone() : null :
  597. null;
  598. };
  599. /**
  600. * Returns a date element given a row and column. In elTable_, the elements that
  601. * represent dates are 1 indexed because of other elements such as headers.
  602. * This corrects for the offset and makes the API 0 indexed.
  603. *
  604. * @param {number} row The row in the element table.
  605. * @param {number} col The column in the element table.
  606. * @return {Element} The element in the grid or null if there is none.
  607. * @protected
  608. */
  609. goog.ui.DatePicker.prototype.getDateElementAt = function(row, col) {
  610. if (row < 0 || col < 0) {
  611. return null;
  612. }
  613. var adjustedRow = row + 1;
  614. return this.elTable_[adjustedRow] ?
  615. this.elTable_[adjustedRow][col + 1] || null :
  616. null;
  617. };
  618. /**
  619. * Sets the selected date. Will always fire the SELECT event.
  620. *
  621. * @param {goog.date.Date|Date} date Date to select or null to select nothing.
  622. */
  623. goog.ui.DatePicker.prototype.setDate = function(date) {
  624. this.setDate_(date, true);
  625. };
  626. /**
  627. * Sets the selected date, and optionally fires the SELECT event based on param.
  628. *
  629. * @param {goog.date.Date|Date} date Date to select or null to select nothing.
  630. * @param {boolean} fireSelection Whether to fire the selection event.
  631. * @private
  632. */
  633. goog.ui.DatePicker.prototype.setDate_ = function(date, fireSelection) {
  634. // Check if the month has been changed.
  635. var sameMonth = date == this.date_ ||
  636. date && this.date_ && date.getFullYear() == this.date_.getFullYear() &&
  637. date.getMonth() == this.date_.getMonth();
  638. // Check if the date has been changed.
  639. var sameDate =
  640. date == this.date_ || sameMonth && date.getDate() == this.date_.getDate();
  641. // Set current date to clone of supplied goog.date.Date or Date.
  642. this.date_ = date && new goog.date.Date(date);
  643. // Set current month
  644. if (date) {
  645. this.activeMonth_.set(this.date_);
  646. // Set years with two digits to their full year, not 19XX.
  647. this.activeMonth_.setFullYear(this.date_.getFullYear());
  648. this.activeMonth_.setDate(1);
  649. }
  650. // Update calendar grid even if the date has not changed as even if today is
  651. // selected another month can be displayed.
  652. this.updateCalendarGrid_();
  653. if (fireSelection) {
  654. // TODO(eae): Standardize selection and change events with other components.
  655. // Fire select event.
  656. var selectEvent = new goog.ui.DatePickerEvent(
  657. goog.ui.DatePicker.Events.SELECT, this, this.date_);
  658. this.dispatchEvent(selectEvent);
  659. }
  660. // Fire change event.
  661. if (!sameDate) {
  662. var changeEvent = new goog.ui.DatePickerEvent(
  663. goog.ui.DatePicker.Events.CHANGE, this, this.date_);
  664. this.dispatchEvent(changeEvent);
  665. }
  666. // Fire change active month event.
  667. if (!sameMonth) {
  668. this.fireChangeActiveMonthEvent_();
  669. }
  670. };
  671. /**
  672. * Updates the navigation row (navigating months and maybe years) in the navRow_
  673. * element of a created picker.
  674. * @private
  675. */
  676. goog.ui.DatePicker.prototype.updateNavigationRow_ = function() {
  677. if (!this.elNavRow_) {
  678. return;
  679. }
  680. var row = this.elNavRow_;
  681. // Clear the navigation row.
  682. while (row.firstChild) {
  683. row.removeChild(row.firstChild);
  684. }
  685. var fullDateFormat =
  686. this.symbols_.DATEFORMATS[goog.i18n.DateTimeFormat.Format.FULL_DATE]
  687. .toLowerCase();
  688. this.renderer_.renderNavigationRow(
  689. row, this.simpleNavigation_, this.showWeekNum_, fullDateFormat);
  690. if (this.simpleNavigation_) {
  691. this.addPreventDefaultClickHandler_(
  692. row, goog.getCssName(this.getBaseCssClass(), 'previousMonth'),
  693. this.previousMonth);
  694. var previousMonthElement = goog.dom.getElementByClass(
  695. goog.getCssName(this.getBaseCssClass(), 'previousMonth'), row);
  696. if (previousMonthElement) {
  697. // Note: we're hiding the next and previous month buttons from screen
  698. // readers because keyboard navigation doesn't currently work correctly
  699. // with them. If that is fixed, we can show the buttons again.
  700. goog.a11y.aria.setState(
  701. previousMonthElement, goog.a11y.aria.State.HIDDEN, true);
  702. previousMonthElement.tabIndex = -1;
  703. }
  704. this.addPreventDefaultClickHandler_(
  705. row, goog.getCssName(this.getBaseCssClass(), 'nextMonth'),
  706. this.nextMonth);
  707. var nextMonthElement = goog.dom.getElementByClass(
  708. goog.getCssName(this.getBaseCssClass(), 'nextMonth'), row);
  709. if (nextMonthElement) {
  710. goog.a11y.aria.setState(
  711. nextMonthElement, goog.a11y.aria.State.HIDDEN, true);
  712. nextMonthElement.tabIndex = -1;
  713. }
  714. this.elMonthYear_ = goog.dom.getElementByClass(
  715. goog.getCssName(this.getBaseCssClass(), 'monthyear'), row);
  716. } else {
  717. this.addPreventDefaultClickHandler_(
  718. row, goog.getCssName(this.getBaseCssClass(), 'previousMonth'),
  719. this.previousMonth);
  720. this.addPreventDefaultClickHandler_(
  721. row, goog.getCssName(this.getBaseCssClass(), 'nextMonth'),
  722. this.nextMonth);
  723. this.addPreventDefaultClickHandler_(
  724. row, goog.getCssName(this.getBaseCssClass(), 'month'),
  725. this.showMonthMenu_);
  726. this.addPreventDefaultClickHandler_(
  727. row, goog.getCssName(this.getBaseCssClass(), 'previousYear'),
  728. this.previousYear);
  729. this.addPreventDefaultClickHandler_(
  730. row, goog.getCssName(this.getBaseCssClass(), 'nextYear'),
  731. this.nextYear);
  732. this.addPreventDefaultClickHandler_(
  733. row, goog.getCssName(this.getBaseCssClass(), 'year'),
  734. this.showYearMenu_);
  735. this.elMonth_ = goog.dom.getElementByClass(
  736. goog.getCssName(this.getBaseCssClass(), 'month'), row);
  737. this.elYear_ = goog.dom.getDomHelper().getElementByClass(
  738. goog.getCssName(this.getBaseCssClass(), 'year'), row);
  739. }
  740. };
  741. /**
  742. * Setup click handler with prevent default.
  743. *
  744. * @param {!Element} parentElement The parent element of the element. This is
  745. * needed because the element in question might not be in the dom yet.
  746. * @param {string} cssName The CSS class name of the element to attach a click
  747. * handler.
  748. * @param {Function} handlerFunction The click handler function.
  749. * @private
  750. */
  751. goog.ui.DatePicker.prototype.addPreventDefaultClickHandler_ = function(
  752. parentElement, cssName, handlerFunction) {
  753. var element = goog.dom.getElementByClass(cssName, parentElement);
  754. this.getHandler().listen(element, goog.events.EventType.CLICK, function(e) {
  755. e.preventDefault();
  756. handlerFunction.call(this, e);
  757. });
  758. };
  759. /**
  760. * Updates the footer row (with select buttons) in the footRow_ element of a
  761. * created picker.
  762. * @private
  763. */
  764. goog.ui.DatePicker.prototype.updateFooterRow_ = function() {
  765. if (!this.elFootRow_) {
  766. return;
  767. }
  768. var row = this.elFootRow_;
  769. // Clear the footer row.
  770. goog.dom.removeChildren(row);
  771. this.renderer_.renderFooterRow(row, this.showWeekNum_);
  772. this.addPreventDefaultClickHandler_(
  773. row, goog.getCssName(this.getBaseCssClass(), 'today-btn'),
  774. this.selectToday);
  775. this.addPreventDefaultClickHandler_(
  776. row, goog.getCssName(this.getBaseCssClass(), 'none-btn'),
  777. this.selectNone);
  778. this.elToday_ = goog.dom.getElementByClass(
  779. goog.getCssName(this.getBaseCssClass(), 'today-btn'), row);
  780. this.elNone_ = goog.dom.getElementByClass(
  781. goog.getCssName(this.getBaseCssClass(), 'none-btn'), row);
  782. this.updateTodayAndNone_();
  783. };
  784. /** @override */
  785. goog.ui.DatePicker.prototype.decorateInternal = function(el) {
  786. goog.ui.DatePicker.superClass_.decorateInternal.call(this, el);
  787. goog.asserts.assert(el);
  788. goog.dom.classlist.add(el, this.getBaseCssClass());
  789. var table = this.dom_.createElement(goog.dom.TagName.TABLE);
  790. var thead = this.dom_.createElement(goog.dom.TagName.THEAD);
  791. var tbody = this.dom_.createElement(goog.dom.TagName.TBODY);
  792. var tfoot = this.dom_.createElement(goog.dom.TagName.TFOOT);
  793. goog.a11y.aria.setRole(tbody, 'grid');
  794. tbody.tabIndex = 0;
  795. // As per comment in colorpicker: table.tBodies and table.tFoot should not be
  796. // used because of a bug in Safari, hence using an instance variable
  797. this.tableBody_ = tbody;
  798. this.tableFoot_ = tfoot;
  799. var row = this.dom_.createElement(goog.dom.TagName.TR);
  800. row.className = goog.getCssName(this.getBaseCssClass(), 'head');
  801. this.elNavRow_ = row;
  802. this.updateNavigationRow_();
  803. thead.appendChild(row);
  804. var cell;
  805. this.elTable_ = [];
  806. for (var i = 0; i < 7; i++) {
  807. row = this.dom_.createElement(goog.dom.TagName.TR);
  808. this.elTable_[i] = [];
  809. for (var j = 0; j < 8; j++) {
  810. cell = this.dom_.createElement(j == 0 || i == 0 ? 'th' : 'td');
  811. if ((j == 0 || i == 0) && j != i) {
  812. cell.className = (j == 0) ?
  813. goog.getCssName(this.getBaseCssClass(), 'week') :
  814. goog.getCssName(this.getBaseCssClass(), 'wday');
  815. goog.a11y.aria.setRole(cell, j == 0 ? 'rowheader' : 'columnheader');
  816. }
  817. row.appendChild(cell);
  818. this.elTable_[i][j] = cell;
  819. }
  820. tbody.appendChild(row);
  821. }
  822. row = this.dom_.createElement(goog.dom.TagName.TR);
  823. row.className = goog.getCssName(this.getBaseCssClass(), 'foot');
  824. this.elFootRow_ = row;
  825. this.updateFooterRow_();
  826. tfoot.appendChild(row);
  827. table.cellSpacing = '0';
  828. table.cellPadding = '0';
  829. table.appendChild(thead);
  830. table.appendChild(tbody);
  831. table.appendChild(tfoot);
  832. el.appendChild(table);
  833. this.redrawWeekdays_();
  834. this.updateCalendarGrid_();
  835. el.tabIndex = 0;
  836. };
  837. /** @override */
  838. goog.ui.DatePicker.prototype.createDom = function() {
  839. goog.ui.DatePicker.superClass_.createDom.call(this);
  840. this.decorateInternal(this.getElement());
  841. };
  842. /** @override */
  843. goog.ui.DatePicker.prototype.enterDocument = function() {
  844. goog.ui.DatePicker.superClass_.enterDocument.call(this);
  845. var eh = this.getHandler();
  846. eh.listen(
  847. this.tableBody_, goog.events.EventType.CLICK, this.handleGridClick_);
  848. eh.listen(
  849. this.getKeyHandlerForElement_(this.getElement()),
  850. goog.events.KeyHandler.EventType.KEY, this.handleGridKeyPress_);
  851. };
  852. /** @override */
  853. goog.ui.DatePicker.prototype.exitDocument = function() {
  854. goog.ui.DatePicker.superClass_.exitDocument.call(this);
  855. this.destroyMenu_();
  856. for (var uid in this.keyHandlers_) {
  857. this.keyHandlers_[uid].dispose();
  858. }
  859. this.keyHandlers_ = {};
  860. };
  861. /**
  862. * @deprecated Use decorate instead.
  863. */
  864. goog.ui.DatePicker.prototype.create = goog.ui.DatePicker.prototype.decorate;
  865. /** @override */
  866. goog.ui.DatePicker.prototype.disposeInternal = function() {
  867. goog.ui.DatePicker.superClass_.disposeInternal.call(this);
  868. this.elTable_ = null;
  869. this.tableBody_ = null;
  870. this.tableFoot_ = null;
  871. this.elNavRow_ = null;
  872. this.elFootRow_ = null;
  873. this.elMonth_ = null;
  874. this.elMonthYear_ = null;
  875. this.elYear_ = null;
  876. this.elToday_ = null;
  877. this.elNone_ = null;
  878. };
  879. /**
  880. * Click handler for date grid.
  881. *
  882. * @param {goog.events.BrowserEvent} event Click event.
  883. * @private
  884. */
  885. goog.ui.DatePicker.prototype.handleGridClick_ = function(event) {
  886. if (event.target.tagName == goog.dom.TagName.TD) {
  887. // colIndex/rowIndex is broken in Safari, find position by looping
  888. var el, x = -2, y = -2; // first col/row is for weekday/weeknum
  889. for (el = event.target; el; el = el.previousSibling, x++) {
  890. }
  891. for (el = event.target.parentNode; el; el = el.previousSibling, y++) {
  892. }
  893. var obj = this.grid_[y][x];
  894. if (this.isUserSelectableDate_(obj)) {
  895. this.setDate(obj.clone());
  896. }
  897. }
  898. };
  899. /**
  900. * Keypress handler for date grid.
  901. *
  902. * @param {goog.events.BrowserEvent} event Keypress event.
  903. * @private
  904. */
  905. goog.ui.DatePicker.prototype.handleGridKeyPress_ = function(event) {
  906. var months, days;
  907. switch (event.keyCode) {
  908. case 33: // Page up
  909. event.preventDefault();
  910. months = -1;
  911. break;
  912. case 34: // Page down
  913. event.preventDefault();
  914. months = 1;
  915. break;
  916. case 37: // Left
  917. event.preventDefault();
  918. days = -1;
  919. break;
  920. case 39: // Right
  921. event.preventDefault();
  922. days = 1;
  923. break;
  924. case 38: // Down
  925. event.preventDefault();
  926. days = -7;
  927. break;
  928. case 40: // Up
  929. event.preventDefault();
  930. days = 7;
  931. break;
  932. case 36: // Home
  933. event.preventDefault();
  934. this.selectToday();
  935. case 46: // Delete
  936. event.preventDefault();
  937. this.selectNone();
  938. break;
  939. case 13: // Enter
  940. case 32: // Space
  941. event.preventDefault();
  942. this.setDate_(this.date_, true /* fireSelection */);
  943. default:
  944. return;
  945. }
  946. var date;
  947. if (this.date_) {
  948. date = this.date_.clone();
  949. date.add(new goog.date.Interval(0, months, days));
  950. } else {
  951. date = this.activeMonth_.clone();
  952. date.setDate(1);
  953. }
  954. if (this.isUserSelectableDate_(date)) {
  955. this.setDate_(date, false /* fireSelection */);
  956. }
  957. };
  958. /**
  959. * Click handler for month button. Opens month selection menu.
  960. *
  961. * @param {goog.events.BrowserEvent} event Click event.
  962. * @private
  963. */
  964. goog.ui.DatePicker.prototype.showMonthMenu_ = function(event) {
  965. event.stopPropagation();
  966. var list = [];
  967. for (var i = 0; i < 12; i++) {
  968. list.push(this.symbols_.STANDALONEMONTHS[i]);
  969. }
  970. this.createMenu_(
  971. this.elMonth_, list, this.handleMonthMenuClick_,
  972. this.symbols_.STANDALONEMONTHS[this.activeMonth_.getMonth()]);
  973. };
  974. /**
  975. * Click handler for year button. Opens year selection menu.
  976. *
  977. * @param {goog.events.BrowserEvent} event Click event.
  978. * @private
  979. */
  980. goog.ui.DatePicker.prototype.showYearMenu_ = function(event) {
  981. event.stopPropagation();
  982. var list = [];
  983. var year = this.activeMonth_.getFullYear();
  984. var loopDate = this.activeMonth_.clone();
  985. for (var i = -goog.ui.DatePicker.YEAR_MENU_RANGE_;
  986. i <= goog.ui.DatePicker.YEAR_MENU_RANGE_; i++) {
  987. loopDate.setFullYear(year + i);
  988. list.push(this.i18nDateFormatterYear_.format(loopDate));
  989. }
  990. this.createMenu_(
  991. this.elYear_, list, this.handleYearMenuClick_,
  992. this.i18nDateFormatterYear_.format(this.activeMonth_));
  993. };
  994. /**
  995. * Call back function for month menu.
  996. *
  997. * @param {Element} target Selected item.
  998. * @private
  999. */
  1000. goog.ui.DatePicker.prototype.handleMonthMenuClick_ = function(target) {
  1001. var itemIndex = Number(target.getAttribute('itemIndex'));
  1002. this.activeMonth_.setMonth(itemIndex);
  1003. this.updateCalendarGrid_();
  1004. if (this.elMonth_.focus) {
  1005. this.elMonth_.focus();
  1006. }
  1007. };
  1008. /**
  1009. * Call back function for year menu.
  1010. *
  1011. * @param {Element} target Selected item.
  1012. * @private
  1013. */
  1014. goog.ui.DatePicker.prototype.handleYearMenuClick_ = function(target) {
  1015. if (target.firstChild.nodeType == goog.dom.NodeType.TEXT) {
  1016. // We use the same technique used for months to get the position of the
  1017. // item in the menu, as the year is not necessarily numeric.
  1018. var itemIndex = Number(target.getAttribute('itemIndex'));
  1019. var year = this.activeMonth_.getFullYear();
  1020. this.activeMonth_.setFullYear(
  1021. year + itemIndex - goog.ui.DatePicker.YEAR_MENU_RANGE_);
  1022. this.updateCalendarGrid_();
  1023. }
  1024. this.elYear_.focus();
  1025. };
  1026. /**
  1027. * Support function for menu creation.
  1028. *
  1029. * @param {Element} srcEl Button to create menu for.
  1030. * @param {Array<string>} items List of items to populate menu with.
  1031. * @param {function(Element)} method Call back method.
  1032. * @param {string} selected Item to mark as selected in menu.
  1033. * @private
  1034. */
  1035. goog.ui.DatePicker.prototype.createMenu_ = function(
  1036. srcEl, items, method, selected) {
  1037. this.destroyMenu_();
  1038. var el = this.dom_.createElement(goog.dom.TagName.DIV);
  1039. el.className = goog.getCssName(this.getBaseCssClass(), 'menu');
  1040. this.menuSelected_ = null;
  1041. var ul = this.dom_.createElement(goog.dom.TagName.UL);
  1042. for (var i = 0; i < items.length; i++) {
  1043. var li = this.dom_.createDom(goog.dom.TagName.LI, null, items[i]);
  1044. li.setAttribute('itemIndex', i);
  1045. if (items[i] == selected) {
  1046. this.menuSelected_ = li;
  1047. }
  1048. ul.appendChild(li);
  1049. }
  1050. el.appendChild(ul);
  1051. srcEl = /** @type {!HTMLElement} */ (srcEl);
  1052. el.style.left = srcEl.offsetLeft + srcEl.parentNode.offsetLeft + 'px';
  1053. el.style.top = srcEl.offsetTop + 'px';
  1054. el.style.width = srcEl.clientWidth + 'px';
  1055. this.elMonth_.parentNode.appendChild(el);
  1056. this.menu_ = el;
  1057. if (!this.menuSelected_) {
  1058. this.menuSelected_ = /** @type {Element} */ (ul.firstChild);
  1059. }
  1060. this.menuSelected_.className =
  1061. goog.getCssName(this.getBaseCssClass(), 'menu-selected');
  1062. this.menuCallback_ = method;
  1063. var eh = this.getHandler();
  1064. eh.listen(this.menu_, goog.events.EventType.CLICK, this.handleMenuClick_);
  1065. eh.listen(
  1066. this.getKeyHandlerForElement_(this.menu_),
  1067. goog.events.KeyHandler.EventType.KEY, this.handleMenuKeyPress_);
  1068. eh.listen(
  1069. this.dom_.getDocument(), goog.events.EventType.CLICK, this.destroyMenu_);
  1070. el.tabIndex = 0;
  1071. el.focus();
  1072. };
  1073. /**
  1074. * Click handler for menu.
  1075. *
  1076. * @param {goog.events.BrowserEvent} event Click event.
  1077. * @private
  1078. */
  1079. goog.ui.DatePicker.prototype.handleMenuClick_ = function(event) {
  1080. event.stopPropagation();
  1081. this.destroyMenu_();
  1082. if (this.menuCallback_) {
  1083. this.menuCallback_(/** @type {Element} */ (event.target));
  1084. }
  1085. };
  1086. /**
  1087. * Keypress handler for menu.
  1088. *
  1089. * @param {goog.events.BrowserEvent} event Keypress event.
  1090. * @private
  1091. */
  1092. goog.ui.DatePicker.prototype.handleMenuKeyPress_ = function(event) {
  1093. // Prevent the grid keypress handler from catching the keypress event.
  1094. event.stopPropagation();
  1095. var el;
  1096. var menuSelected = this.menuSelected_;
  1097. switch (event.keyCode) {
  1098. case 35: // End
  1099. event.preventDefault();
  1100. el = menuSelected.parentNode.lastChild;
  1101. break;
  1102. case 36: // Home
  1103. event.preventDefault();
  1104. el = menuSelected.parentNode.firstChild;
  1105. break;
  1106. case 38: // Up
  1107. event.preventDefault();
  1108. el = menuSelected.previousSibling;
  1109. break;
  1110. case 40: // Down
  1111. event.preventDefault();
  1112. el = menuSelected.nextSibling;
  1113. break;
  1114. case 13: // Enter
  1115. case 9: // Tab
  1116. case 0: // Space
  1117. event.preventDefault();
  1118. this.destroyMenu_();
  1119. this.menuCallback_(menuSelected);
  1120. break;
  1121. }
  1122. if (el && el != menuSelected) {
  1123. menuSelected.className = '';
  1124. el.className = goog.getCssName(this.getBaseCssClass(), 'menu-selected');
  1125. this.menuSelected_ = /** @type {!Element} */ (el);
  1126. }
  1127. };
  1128. /**
  1129. * Support function for menu destruction.
  1130. * @private
  1131. */
  1132. goog.ui.DatePicker.prototype.destroyMenu_ = function() {
  1133. if (this.menu_) {
  1134. var eh = this.getHandler();
  1135. eh.unlisten(this.menu_, goog.events.EventType.CLICK, this.handleMenuClick_);
  1136. eh.unlisten(
  1137. this.getKeyHandlerForElement_(this.menu_),
  1138. goog.events.KeyHandler.EventType.KEY, this.handleMenuKeyPress_);
  1139. eh.unlisten(
  1140. this.dom_.getDocument(), goog.events.EventType.CLICK,
  1141. this.destroyMenu_);
  1142. goog.dom.removeNode(this.menu_);
  1143. delete this.menu_;
  1144. }
  1145. };
  1146. /**
  1147. * Determines the dates/weekdays for the current month and builds an in memory
  1148. * representation of the calendar.
  1149. *
  1150. * @private
  1151. */
  1152. goog.ui.DatePicker.prototype.updateCalendarGrid_ = function() {
  1153. if (!this.getElement()) {
  1154. return;
  1155. }
  1156. var date = this.activeMonth_.clone();
  1157. date.setDate(1);
  1158. // Show year name of select month
  1159. if (this.elMonthYear_) {
  1160. goog.dom.setTextContent(
  1161. this.elMonthYear_, this.i18nDateFormatterMonthYear_.format(date));
  1162. }
  1163. if (this.elMonth_) {
  1164. goog.dom.setTextContent(
  1165. this.elMonth_, this.symbols_.STANDALONEMONTHS[date.getMonth()]);
  1166. }
  1167. if (this.elYear_) {
  1168. goog.dom.setTextContent(
  1169. this.elYear_, this.i18nDateFormatterYear_.format(date));
  1170. }
  1171. var wday = date.getWeekday();
  1172. var days = date.getNumberOfDaysInMonth();
  1173. // Determine how many days to show for previous month
  1174. date.add(new goog.date.Interval(goog.date.Interval.MONTHS, -1));
  1175. date.setDate(date.getNumberOfDaysInMonth() - (wday - 1));
  1176. if (this.showFixedNumWeeks_ && !this.extraWeekAtEnd_ && days + wday < 33) {
  1177. date.add(new goog.date.Interval(goog.date.Interval.DAYS, -7));
  1178. }
  1179. // Create weekday/day grid
  1180. var dayInterval = new goog.date.Interval(goog.date.Interval.DAYS, 1);
  1181. this.grid_ = [];
  1182. for (var y = 0; y < 6; y++) { // Weeks
  1183. this.grid_[y] = [];
  1184. for (var x = 0; x < 7; x++) { // Weekdays
  1185. this.grid_[y][x] = date.clone();
  1186. // Date.add breaks dates before year 100 by adding 1900 to the year
  1187. // value. As a workaround we store the year before the add and reapply it
  1188. // after (with special handling for January 1st).
  1189. var year = date.getFullYear();
  1190. date.add(dayInterval);
  1191. if (date.getMonth() == 0 && date.getDate() == 1) {
  1192. // Increase year on January 1st.
  1193. year++;
  1194. }
  1195. date.setFullYear(year);
  1196. }
  1197. }
  1198. this.redrawCalendarGrid_();
  1199. };
  1200. /**
  1201. * Draws calendar view from in memory representation and applies class names
  1202. * depending on the selection, weekday and whatever the day belongs to the
  1203. * active month or not.
  1204. * @private
  1205. */
  1206. goog.ui.DatePicker.prototype.redrawCalendarGrid_ = function() {
  1207. if (!this.getElement()) {
  1208. return;
  1209. }
  1210. var month = this.activeMonth_.getMonth();
  1211. var today = new goog.date.Date();
  1212. var todayYear = today.getFullYear();
  1213. var todayMonth = today.getMonth();
  1214. var todayDate = today.getDate();
  1215. // Draw calendar week by week, a worst case month has six weeks.
  1216. for (var y = 0; y < 6; y++) {
  1217. // Draw week number, if enabled
  1218. if (this.showWeekNum_) {
  1219. goog.dom.setTextContent(
  1220. this.elTable_[y + 1][0],
  1221. this.i18nDateFormatterWeek_.format(this.grid_[y][0]));
  1222. goog.dom.classlist.set(
  1223. this.elTable_[y + 1][0],
  1224. goog.getCssName(this.getBaseCssClass(), 'week'));
  1225. } else {
  1226. goog.dom.setTextContent(this.elTable_[y + 1][0], '');
  1227. goog.dom.classlist.set(this.elTable_[y + 1][0], '');
  1228. }
  1229. for (var x = 0; x < 7; x++) {
  1230. var o = this.grid_[y][x];
  1231. var el = this.elTable_[y + 1][x + 1];
  1232. // Assign a unique element id (required for setting the active descendant
  1233. // ARIA role) unless already set.
  1234. if (!el.id) {
  1235. el.id = this.cellIdGenerator_.getNextUniqueId();
  1236. }
  1237. goog.asserts.assert(el, 'The table DOM element cannot be null.');
  1238. goog.a11y.aria.setRole(el, 'gridcell');
  1239. // Set the aria label of the grid cell to the month plus the day.
  1240. goog.a11y.aria.setLabel(
  1241. el, this.i18nDateFormatterDayAriaLabel_.format(o));
  1242. var classes = [goog.getCssName(this.getBaseCssClass(), 'date')];
  1243. if (!this.isUserSelectableDate_(o)) {
  1244. classes.push(
  1245. goog.getCssName(this.getBaseCssClass(), 'unavailable-date'));
  1246. }
  1247. if (this.showOtherMonths_ || o.getMonth() == month) {
  1248. // Date belongs to previous or next month
  1249. if (o.getMonth() != month) {
  1250. classes.push(goog.getCssName(this.getBaseCssClass(), 'other-month'));
  1251. }
  1252. // Apply styles set by setWeekdayClass
  1253. var wday = (x + this.activeMonth_.getFirstDayOfWeek() + 7) % 7;
  1254. if (this.wdayStyles_[wday]) {
  1255. classes.push(this.wdayStyles_[wday]);
  1256. }
  1257. // Current date
  1258. if (o.getDate() == todayDate && o.getMonth() == todayMonth &&
  1259. o.getFullYear() == todayYear) {
  1260. classes.push(goog.getCssName(this.getBaseCssClass(), 'today'));
  1261. }
  1262. // Selected date
  1263. if (this.date_ && o.getDate() == this.date_.getDate() &&
  1264. o.getMonth() == this.date_.getMonth() &&
  1265. o.getFullYear() == this.date_.getFullYear()) {
  1266. classes.push(goog.getCssName(this.getBaseCssClass(), 'selected'));
  1267. goog.asserts.assert(
  1268. this.tableBody_, 'The table body DOM element cannot be null');
  1269. goog.a11y.aria.setState(this.tableBody_, 'activedescendant', el.id);
  1270. }
  1271. // Custom decorator
  1272. if (this.decoratorFunction_) {
  1273. var customClass = this.decoratorFunction_(o);
  1274. if (customClass) {
  1275. classes.push(customClass);
  1276. }
  1277. }
  1278. // Set cell text to the date and apply classes.
  1279. var formatedDate = this.longDateFormat_ ?
  1280. this.i18nDateFormatterDay2_.format(o) :
  1281. this.i18nDateFormatterDay_.format(o);
  1282. goog.dom.setTextContent(el, formatedDate);
  1283. // Date belongs to previous or next month and showOtherMonths is false,
  1284. // clear text and classes.
  1285. } else {
  1286. goog.dom.setTextContent(el, '');
  1287. }
  1288. goog.dom.classlist.set(el, classes.join(' '));
  1289. }
  1290. // Hide the either the last one or last two weeks if they contain no days
  1291. // from the active month and the showFixedNumWeeks is false. The first four
  1292. // weeks are always shown as no month has less than 28 days).
  1293. if (y >= 4) {
  1294. var parentEl = /** @type {Element} */ (
  1295. this.elTable_[y + 1][0].parentElement ||
  1296. this.elTable_[y + 1][0].parentNode);
  1297. goog.style.setElementShown(
  1298. parentEl,
  1299. this.grid_[y][0].getMonth() == month || this.showFixedNumWeeks_);
  1300. }
  1301. }
  1302. };
  1303. /**
  1304. * Fires the CHANGE_ACTIVE_MONTH event.
  1305. * @private
  1306. */
  1307. goog.ui.DatePicker.prototype.fireChangeActiveMonthEvent_ = function() {
  1308. var changeMonthEvent = new goog.ui.DatePickerEvent(
  1309. goog.ui.DatePicker.Events.CHANGE_ACTIVE_MONTH, this,
  1310. this.getActiveMonth());
  1311. this.dispatchEvent(changeMonthEvent);
  1312. };
  1313. /**
  1314. * Draw weekday names, if enabled. Start with whatever day has been set as the
  1315. * first day of week.
  1316. * @private
  1317. */
  1318. goog.ui.DatePicker.prototype.redrawWeekdays_ = function() {
  1319. if (!this.getElement()) {
  1320. return;
  1321. }
  1322. if (this.showWeekdays_) {
  1323. for (var x = 0; x < 7; x++) {
  1324. var el = this.elTable_[0][x + 1];
  1325. var wday = (x + this.activeMonth_.getFirstDayOfWeek() + 7) % 7;
  1326. goog.dom.setTextContent(el, this.wdayNames_[(wday + 1) % 7]);
  1327. }
  1328. }
  1329. var parentEl = /** @type {Element} */ (
  1330. this.elTable_[0][0].parentElement || this.elTable_[0][0].parentNode);
  1331. goog.style.setElementShown(parentEl, this.showWeekdays_);
  1332. };
  1333. /**
  1334. * Returns the key handler for an element and caches it so that it can be
  1335. * retrieved at a later point.
  1336. * @param {Element} el The element to get the key handler for.
  1337. * @return {goog.events.KeyHandler} The key handler for the element.
  1338. * @private
  1339. */
  1340. goog.ui.DatePicker.prototype.getKeyHandlerForElement_ = function(el) {
  1341. var uid = goog.getUid(el);
  1342. if (!(uid in this.keyHandlers_)) {
  1343. this.keyHandlers_[uid] = new goog.events.KeyHandler(el);
  1344. }
  1345. return this.keyHandlers_[uid];
  1346. };
  1347. /**
  1348. * Object representing a date picker event.
  1349. *
  1350. * @param {string} type Event type.
  1351. * @param {goog.ui.DatePicker} target Date picker initiating event.
  1352. * @param {goog.date.Date} date Selected date.
  1353. * @constructor
  1354. * @extends {goog.events.Event}
  1355. * @final
  1356. */
  1357. goog.ui.DatePickerEvent = function(type, target, date) {
  1358. goog.events.Event.call(this, type, target);
  1359. /**
  1360. * The selected date
  1361. * @type {goog.date.Date}
  1362. */
  1363. this.date = date;
  1364. };
  1365. goog.inherits(goog.ui.DatePickerEvent, goog.events.Event);