CalendarLite.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. require({cache:{
  2. 'url:dijit/templates/Calendar.html':"<table cellspacing=\"0\" cellpadding=\"0\" class=\"dijitCalendarContainer\" role=\"grid\" aria-labelledby=\"${id}_mddb ${id}_year\">\n\t<thead>\n\t\t<tr class=\"dijitReset dijitCalendarMonthContainer\" valign=\"top\">\n\t\t\t<th class='dijitReset dijitCalendarArrow' data-dojo-attach-point=\"decrementMonth\">\n\t\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitCalendarIncrementControl dijitCalendarDecrease\" role=\"presentation\"/>\n\t\t\t\t<span data-dojo-attach-point=\"decreaseArrowNode\" class=\"dijitA11ySideArrow\">-</span>\n\t\t\t</th>\n\t\t\t<th class='dijitReset' colspan=\"5\">\n\t\t\t\t<div data-dojo-attach-point=\"monthNode\">\n\t\t\t\t</div>\n\t\t\t</th>\n\t\t\t<th class='dijitReset dijitCalendarArrow' data-dojo-attach-point=\"incrementMonth\">\n\t\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitCalendarIncrementControl dijitCalendarIncrease\" role=\"presentation\"/>\n\t\t\t\t<span data-dojo-attach-point=\"increaseArrowNode\" class=\"dijitA11ySideArrow\">+</span>\n\t\t\t</th>\n\t\t</tr>\n\t\t<tr>\n\t\t\t${!dayCellsHtml}\n\t\t</tr>\n\t</thead>\n\t<tbody data-dojo-attach-point=\"dateRowsNode\" data-dojo-attach-event=\"onclick: _onDayClick\" class=\"dijitReset dijitCalendarBodyContainer\">\n\t\t\t${!dateRowsHtml}\n\t</tbody>\n\t<tfoot class=\"dijitReset dijitCalendarYearContainer\">\n\t\t<tr>\n\t\t\t<td class='dijitReset' valign=\"top\" colspan=\"7\" role=\"presentation\">\n\t\t\t\t<div class=\"dijitCalendarYearLabel\">\n\t\t\t\t\t<span data-dojo-attach-point=\"previousYearLabelNode\" class=\"dijitInline dijitCalendarPreviousYear\" role=\"button\"></span>\n\t\t\t\t\t<span data-dojo-attach-point=\"currentYearLabelNode\" class=\"dijitInline dijitCalendarSelectedYear\" role=\"button\" id=\"${id}_year\"></span>\n\t\t\t\t\t<span data-dojo-attach-point=\"nextYearLabelNode\" class=\"dijitInline dijitCalendarNextYear\" role=\"button\"></span>\n\t\t\t\t</div>\n\t\t\t</td>\n\t\t</tr>\n\t</tfoot>\n</table>\n"}});
  3. define("dijit/CalendarLite", [
  4. "dojo/_base/array", // array.forEach array.map
  5. "dojo/_base/declare", // declare
  6. "dojo/cldr/supplemental", // cldrSupplemental.getFirstDayOfWeek
  7. "dojo/date", // date
  8. "dojo/date/locale",
  9. "dojo/dom", // dom.setSelectable
  10. "dojo/dom-class", // domClass.contains
  11. "dojo/_base/event", // event.stop
  12. "dojo/_base/lang", // lang.getObject, lang.hitch
  13. "dojo/_base/sniff", // has("ie") has("webkit")
  14. "dojo/string", // string.substitute
  15. "dojo/_base/window", // win.doc.createTextNode
  16. "./_WidgetBase",
  17. "./_TemplatedMixin",
  18. "dojo/text!./templates/Calendar.html"
  19. ], function(array, declare, cldrSupplemental, date, local, dom, domClass, event, lang, has, string, win,
  20. _WidgetBase, _TemplatedMixin, template){
  21. /*=====
  22. var _WidgetBase = dijit._WidgetBase;
  23. var _TemplatedMixin = dijit._TemplatedMixin;
  24. =====*/
  25. // module:
  26. // dijit/CalendarLite
  27. // summary:
  28. // Lightweight version of Calendar widget aimed towards mobile use
  29. var CalendarLite = declare("dijit.CalendarLite", [_WidgetBase, _TemplatedMixin], {
  30. // summary:
  31. // Lightweight version of Calendar widget aimed towards mobile use
  32. //
  33. // description:
  34. // A simple GUI for choosing a date in the context of a monthly calendar.
  35. // This widget can't be used in a form because it doesn't serialize the date to an
  36. // `<input>` field. For a form element, use dijit.form.DateTextBox instead.
  37. //
  38. // Note that the parser takes all dates attributes passed in the
  39. // [RFC 3339 format](http://www.faqs.org/rfcs/rfc3339.html), e.g. `2005-06-30T08:05:00-07:00`
  40. // so that they are serializable and locale-independent.
  41. //
  42. // Also note that this widget isn't keyboard accessible; use dijit.Calendar for that
  43. // example:
  44. // | var calendar = new dijit.CalendarLite({}, dojo.byId("calendarNode"));
  45. //
  46. // example:
  47. // | <div data-dojo-type="dijit.CalendarLite"></div>
  48. // Template for main calendar
  49. templateString: template,
  50. // Template for cell for a day of the week (ex: M)
  51. dowTemplateString: '<th class="dijitReset dijitCalendarDayLabelTemplate" role="columnheader"><span class="dijitCalendarDayLabel">${d}</span></th>',
  52. // Templates for a single date (ex: 13), and for a row for a week (ex: 20 21 22 23 24 25 26)
  53. dateTemplateString: '<td class="dijitReset" role="gridcell" data-dojo-attach-point="dateCells"><span class="dijitCalendarDateLabel" data-dojo-attach-point="dateLabels"></span></td>',
  54. weekTemplateString: '<tr class="dijitReset dijitCalendarWeekTemplate" role="row">${d}${d}${d}${d}${d}${d}${d}</tr>',
  55. // value: Date
  56. // The currently selected Date, initially set to invalid date to indicate no selection.
  57. value: new Date(""),
  58. // TODO: for 2.0 make this a string (ISO format) rather than a Date
  59. // datePackage: String
  60. // JavaScript object containing Calendar functions. Uses Gregorian Calendar routines
  61. // from dojo.date by default.
  62. datePackage: date,
  63. // dayWidth: String
  64. // How to represent the days of the week in the calendar header. See locale
  65. dayWidth: "narrow",
  66. // tabIndex: Integer
  67. // Order fields are traversed when user hits the tab key
  68. tabIndex: "0",
  69. // currentFocus: Date
  70. // Date object containing the currently focused date, or the date which would be focused
  71. // if the calendar itself was focused. Also indicates which year and month to display,
  72. // i.e. the current "page" the calendar is on.
  73. currentFocus: new Date(),
  74. baseClass:"dijitCalendar",
  75. _isValidDate: function(/*Date*/ value){
  76. // summary:
  77. // Runs various tests on the value, checking that it's a valid date, rather
  78. // than blank or NaN.
  79. // tags:
  80. // private
  81. return value && !isNaN(value) && typeof value == "object" &&
  82. value.toString() != this.constructor.prototype.value.toString();
  83. },
  84. _getValueAttr: function(){
  85. // summary:
  86. // Support get('value')
  87. // this.value is set to 1AM, but return midnight, local time for back-compat
  88. if(this.value && !isNaN(this.value)){
  89. var value = new this.dateClassObj(this.value);
  90. value.setHours(0, 0, 0, 0);
  91. // If daylight savings pushes midnight to the previous date, fix the Date
  92. // object to point at 1am so it will represent the correct day. See #9366
  93. if(value.getDate() < this.value.getDate()){
  94. value = this.dateFuncObj.add(value, "hour", 1);
  95. }
  96. return value;
  97. }else{
  98. return null;
  99. }
  100. },
  101. _setValueAttr: function(/*Date|Number*/ value, /*Boolean*/ priorityChange){
  102. // summary:
  103. // Support set("value", ...)
  104. // description:
  105. // Set the current date and update the UI. If the date is disabled, the value will
  106. // not change, but the display will change to the corresponding month.
  107. // value:
  108. // Either a Date or the number of seconds since 1970.
  109. // tags:
  110. // protected
  111. if(value){
  112. // convert from Number to Date, or make copy of Date object so that setHours() call below
  113. // doesn't affect original value
  114. value = new this.dateClassObj(value);
  115. }
  116. if(this._isValidDate(value)){
  117. if(!this._isValidDate(this.value) || this.dateFuncObj.compare(value, this.value)){
  118. value.setHours(1, 0, 0, 0); // round to nearest day (1am to avoid issues when DST shift occurs at midnight, see #8521, #9366)
  119. if(!this.isDisabledDate(value, this.lang)){
  120. this._set("value", value);
  121. // Set focus cell to the new value. Arguably this should only happen when there isn't a current
  122. // focus point. This will also repopulate the grid, showing the new selected value (and possibly
  123. // new month/year).
  124. this.set("currentFocus", value);
  125. if(priorityChange || typeof priorityChange == "undefined"){
  126. this.onChange(this.get('value'));
  127. }
  128. }
  129. }
  130. }else{
  131. // clear value, and repopulate grid (to deselect the previously selected day) without changing currentFocus
  132. this._set("value", null);
  133. this.set("currentFocus", this.currentFocus);
  134. }
  135. },
  136. _setText: function(node, text){
  137. // summary:
  138. // This just sets the content of node to the specified text.
  139. // Can't do "node.innerHTML=text" because of an IE bug w/tables, see #3434.
  140. // tags:
  141. // private
  142. while(node.firstChild){
  143. node.removeChild(node.firstChild);
  144. }
  145. node.appendChild(win.doc.createTextNode(text));
  146. },
  147. _populateGrid: function(){
  148. // summary:
  149. // Fills in the calendar grid with each day (1-31)
  150. // tags:
  151. // private
  152. var month = new this.dateClassObj(this.currentFocus);
  153. month.setDate(1);
  154. var firstDay = month.getDay(),
  155. daysInMonth = this.dateFuncObj.getDaysInMonth(month),
  156. daysInPreviousMonth = this.dateFuncObj.getDaysInMonth(this.dateFuncObj.add(month, "month", -1)),
  157. today = new this.dateClassObj(),
  158. dayOffset = cldrSupplemental.getFirstDayOfWeek(this.lang);
  159. if(dayOffset > firstDay){ dayOffset -= 7; }
  160. // Mapping from date (as specified by number returned from Date.valueOf()) to corresponding <td>
  161. this._date2cell = {};
  162. // Iterate through dates in the calendar and fill in date numbers and style info
  163. array.forEach(this.dateCells, function(template, idx){
  164. var i = idx + dayOffset;
  165. var date = new this.dateClassObj(month),
  166. number, clazz = "dijitCalendar", adj = 0;
  167. if(i < firstDay){
  168. number = daysInPreviousMonth - firstDay + i + 1;
  169. adj = -1;
  170. clazz += "Previous";
  171. }else if(i >= (firstDay + daysInMonth)){
  172. number = i - firstDay - daysInMonth + 1;
  173. adj = 1;
  174. clazz += "Next";
  175. }else{
  176. number = i - firstDay + 1;
  177. clazz += "Current";
  178. }
  179. if(adj){
  180. date = this.dateFuncObj.add(date, "month", adj);
  181. }
  182. date.setDate(number);
  183. if(!this.dateFuncObj.compare(date, today, "date")){
  184. clazz = "dijitCalendarCurrentDate " + clazz;
  185. }
  186. if(this._isSelectedDate(date, this.lang)){
  187. clazz = "dijitCalendarSelectedDate " + clazz;
  188. template.setAttribute("aria-selected", true);
  189. }else{
  190. template.setAttribute("aria-selected", false);
  191. }
  192. if(this.isDisabledDate(date, this.lang)){
  193. clazz = "dijitCalendarDisabledDate " + clazz;
  194. template.setAttribute("aria-disabled", true);
  195. }else{
  196. clazz = "dijitCalendarEnabledDate " + clazz;
  197. template.removeAttribute("aria-disabled");
  198. }
  199. var clazz2 = this.getClassForDate(date, this.lang);
  200. if(clazz2){
  201. clazz = clazz2 + " " + clazz;
  202. }
  203. template.className = clazz + "Month dijitCalendarDateTemplate";
  204. // Each cell has an associated integer value representing it's date
  205. var dateVal = date.valueOf();
  206. this._date2cell[dateVal] = template;
  207. template.dijitDateValue = dateVal;
  208. // Set Date string (ex: "13").
  209. this._setText(this.dateLabels[idx], date.getDateLocalized ? date.getDateLocalized(this.lang) : date.getDate());
  210. }, this);
  211. // set name of this month
  212. this.monthWidget.set("month", month);
  213. // Fill in localized prev/current/next years
  214. var y = month.getFullYear() - 1;
  215. var d = new this.dateClassObj();
  216. array.forEach(["previous", "current", "next"], function(name){
  217. d.setFullYear(y++);
  218. this._setText(this[name+"YearLabelNode"],
  219. this.dateLocaleModule.format(d, {selector:'year', locale:this.lang}));
  220. }, this);
  221. },
  222. goToToday: function(){
  223. // summary:
  224. // Sets calendar's value to today's date
  225. this.set('value', new this.dateClassObj());
  226. },
  227. constructor: function(/*Object*/args){
  228. this.datePackage = args.datePackage || this.datePackage;
  229. this.dateFuncObj = typeof this.datePackage == "string" ?
  230. lang.getObject(this.datePackage, false) :// "string" part for back-compat, remove for 2.0
  231. this.datePackage;
  232. this.dateClassObj = this.dateFuncObj.Date || Date;
  233. this.dateLocaleModule = lang.getObject("locale", false, this.dateFuncObj);
  234. },
  235. _createMonthWidget: function(){
  236. // summary:
  237. // Creates the drop down button that displays the current month and lets user pick a new one
  238. return CalendarLite._MonthWidget({
  239. id: this.id + "_mw",
  240. lang: this.lang,
  241. dateLocaleModule: this.dateLocaleModule
  242. }, this.monthNode);
  243. },
  244. buildRendering: function(){
  245. // Markup for days of the week (referenced from template)
  246. var d = this.dowTemplateString,
  247. dayNames = this.dateLocaleModule.getNames('days', this.dayWidth, 'standAlone', this.lang),
  248. dayOffset = cldrSupplemental.getFirstDayOfWeek(this.lang);
  249. this.dayCellsHtml = string.substitute([d,d,d,d,d,d,d].join(""), {d: ""}, function(){
  250. return dayNames[dayOffset++ % 7]
  251. });
  252. // Markup for dates of the month (referenced from template), but without numbers filled in
  253. var r = string.substitute(this.weekTemplateString, {d: this.dateTemplateString});
  254. this.dateRowsHtml = [r,r,r,r,r,r].join("");
  255. // Instantiate from template.
  256. // dateCells and dateLabels arrays filled when _Templated parses my template.
  257. this.dateCells = [];
  258. this.dateLabels = [];
  259. this.inherited(arguments);
  260. dom.setSelectable(this.domNode, false);
  261. var dateObj = new this.dateClassObj(this.currentFocus);
  262. this._supportingWidgets.push(this.monthWidget = this._createMonthWidget());
  263. this.set('currentFocus', dateObj, false); // draw the grid to the month specified by currentFocus
  264. // Set up connects for increment/decrement of months/years
  265. var connect = lang.hitch(this, function(nodeProp, part, amount){
  266. this.connect(this[nodeProp], "onclick", function(){
  267. this._setCurrentFocusAttr(this.dateFuncObj.add(this.currentFocus, part, amount));
  268. });
  269. });
  270. connect("incrementMonth", "month", 1);
  271. connect("decrementMonth", "month", -1);
  272. connect("nextYearLabelNode", "year", 1);
  273. connect("previousYearLabelNode", "year", -1);
  274. },
  275. _setCurrentFocusAttr: function(/*Date*/ date, /*Boolean*/ forceFocus){
  276. // summary:
  277. // If the calendar currently has focus, then focuses specified date,
  278. // changing the currently displayed month/year if necessary.
  279. // If the calendar doesn't have focus, updates currently
  280. // displayed month/year, and sets the cell that will get focus.
  281. // forceFocus:
  282. // If true, will focus() the cell even if calendar itself doesn't have focus
  283. var oldFocus = this.currentFocus,
  284. oldCell = oldFocus && this._date2cell ? this._date2cell[oldFocus.valueOf()] : null;
  285. // round specified value to nearest day (1am to avoid issues when DST shift occurs at midnight, see #8521, #9366)
  286. date = new this.dateClassObj(date);
  287. date.setHours(1, 0, 0, 0);
  288. this._set("currentFocus", date);
  289. // TODO: only re-populate grid when month/year has changed
  290. this._populateGrid();
  291. // set tabIndex=0 on new cell, and focus it (but only if Calendar itself is focused)
  292. var newCell = this._date2cell[date.valueOf()];
  293. newCell.setAttribute("tabIndex", this.tabIndex);
  294. if(this.focused || forceFocus){
  295. newCell.focus();
  296. }
  297. // set tabIndex=-1 on old focusable cell
  298. if(oldCell && oldCell != newCell){
  299. if(has("webkit")){ // see #11064 about webkit bug
  300. oldCell.setAttribute("tabIndex", "-1");
  301. }else{
  302. oldCell.removeAttribute("tabIndex");
  303. }
  304. }
  305. },
  306. focus: function(){
  307. // summary:
  308. // Focus the calendar by focusing one of the calendar cells
  309. this._setCurrentFocusAttr(this.currentFocus, true);
  310. },
  311. _onDayClick: function(/*Event*/ evt){
  312. // summary:
  313. // Handler for day clicks, selects the date if appropriate
  314. // tags:
  315. // protected
  316. event.stop(evt);
  317. for(var node = evt.target; node && !node.dijitDateValue; node = node.parentNode);
  318. if(node && !domClass.contains(node, "dijitCalendarDisabledDate")){
  319. this.set('value', node.dijitDateValue);
  320. }
  321. },
  322. onChange: function(/*Date*/ /*===== date =====*/){
  323. // summary:
  324. // Called only when the selected date has changed
  325. },
  326. _isSelectedDate: function(dateObject /*===== , locale =====*/){
  327. // summary:
  328. // Extension point so developers can subclass Calendar to
  329. // support multiple (concurrently) selected dates
  330. // dateObject: Date
  331. // locale: String?
  332. // tags:
  333. // protected extension
  334. return this._isValidDate(this.value) && !this.dateFuncObj.compare(dateObject, this.value, "date")
  335. },
  336. isDisabledDate: function(/*===== dateObject, locale =====*/){
  337. // summary:
  338. // May be overridden to disable certain dates in the calendar e.g. `isDisabledDate=dojo.date.locale.isWeekend`
  339. // dateObject: Date
  340. // locale: String?
  341. // tags:
  342. // extension
  343. /*=====
  344. return false; // Boolean
  345. =====*/
  346. },
  347. getClassForDate: function(/*===== dateObject, locale =====*/){
  348. // summary:
  349. // May be overridden to return CSS classes to associate with the date entry for the given dateObject,
  350. // for example to indicate a holiday in specified locale.
  351. // dateObject: Date
  352. // locale: String?
  353. // tags:
  354. // extension
  355. /*=====
  356. return ""; // String
  357. =====*/
  358. }
  359. });
  360. CalendarLite._MonthWidget = declare("dijit.CalendarLite._MonthWidget", _WidgetBase, {
  361. // summary:
  362. // Displays name of current month padded to the width of the month
  363. // w/the longest name, so that changing months doesn't change width.
  364. //
  365. // Create as new dijit.Calendar._MonthWidget({
  366. // lang: ...,
  367. // dateLocaleModule: ...
  368. // })
  369. _setMonthAttr: function(month){
  370. // summary:
  371. // Set the current month to display as a label
  372. var monthNames = this.dateLocaleModule.getNames('months', 'wide', 'standAlone', this.lang, month),
  373. spacer =
  374. (has("ie") == 6 ? "" : "<div class='dijitSpacer'>" +
  375. array.map(monthNames, function(s){ return "<div>" + s + "</div>"; }).join("") + "</div>");
  376. // Set name of current month and also fill in spacer element with all the month names
  377. // (invisible) so that the maximum width will affect layout. But not on IE6 because then
  378. // the center <TH> overlaps the right <TH> (due to a browser bug).
  379. this.domNode.innerHTML =
  380. spacer +
  381. "<div class='dijitCalendarMonthLabel dijitCalendarCurrentMonthLabel'>" +
  382. monthNames[month.getMonth()] + "</div>";
  383. }
  384. });
  385. return CalendarLite;
  386. });