source: Dev/branches/rest-dojo-ui/client/dijit/CalendarLite.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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