CalendarLite.js.uncompressed.js 19 KB

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