source: Dev/branches/rest-dojo-ui/client/dojox/widget/MultiSelectCalendar.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: 34.3 KB
Line 
1define("dojox/widget/MultiSelectCalendar", [
2    "dojo/main", "dijit",
3    "dojo/text!./MultiSelectCalendar/MultiSelectCalendar.html",
4    "dojo/cldr/supplemental",
5    "dojo/date",
6    "dojo/date/locale",
7    "dijit/_Widget", "dijit/_Templated", "dijit/_CssStateMixin", "dijit/form/DropDownButton", "dijit/typematic"],
8    function(dojo, dijit, template) {
9
10dojo.experimental("dojox.widget.MultiSelectCalendar");
11
12dojo.declare(
13        "dojox.widget.MultiSelectCalendar",
14        [dijit._Widget, dijit._TemplatedMixin, dijit._WidgetsInTemplateMixin, dijit._CssStateMixin],
15        {
16                // summary:
17                //              A simple GUI for choosing several dates in the context of a monthly calendar.
18                //
19                // description:
20                //              A simple GUI for choosing several dates in the context of a monthly calendar.
21                //              This widget serialises its selected dates to ISO dates or ISO ranges of dates,
22                //              depending on developer selection
23                //              Note that it accepts an Array of ISO dates as its input
24                //
25                // example:
26                //      |       var calendar = new dojox.widget.MultiSelectCalendar({value: ['2011-05-07,'2011-05-08',2011-05-09','2011-05-23']}, dojo.byId("calendarNode"));
27                //
28                // example:
29                //      |       <div dojoType="dojox.widget.MultiSelectCalendar"></div>
30
31                templateString: template,
32                widgetsInTemplate: true,
33
34                // value: Date
35                //              The currently selected Dates, initially set to an empty object to indicate no selection.
36                value: {},
37
38                // datePackage: String
39                //              JavaScript namespace to find Calendar routines.  Uses Gregorian Calendar routines
40                //              at dojo.date by default.
41                datePackage: "dojo.date",
42
43                // dayWidth: String
44                //              How to represent the days of the week in the calendar header. See dojo.date.locale
45                dayWidth: "narrow",
46
47                // tabIndex: Integer
48                //              Order fields are traversed when user hits the tab key
49                tabIndex: "0",
50               
51                // if returnIsoRanges is true, the selected dates will be returned as ISO ranges
52                // else each selected date will be returned sequentially
53                returnIsoRanges : false,
54               
55                // currentFocus: Date
56                //              Date object containing the currently focused date, or the date which would be focused
57                //              if the calendar itself was focused.   Also indicates which year and month to display,
58                //              i.e. the current "page" the calendar is on.
59                currentFocus: new Date(),
60
61                baseClass:"dijitCalendar",
62               
63                cssStateNodes: {
64                        "decrementMonth": "dijitCalendarArrow",
65                        "incrementMonth": "dijitCalendarArrow",
66                        "previousYearLabelNode": "dijitCalendarPreviousYear",
67                        "nextYearLabelNode": "dijitCalendarNextYear"                   
68                },
69
70                _areValidDates: function(/*Date*/ value){
71                        // summary:
72                        //              Runs various tests on each selected date, checking that they're a valid date, rather
73                        //              than blank or NaN.
74                        // tags:
75                        //              private
76                        for (var selDate in this.value){
77                                valid = (selDate && !isNaN(selDate) && typeof value == "object" && selDate.toString() != this.constructor.prototype.value.toString());
78                                if(!valid){ return false; }
79                        }
80                        return true;
81                },
82
83                _getValueAttr: function(){
84                        // summary: this method returns the list of selected dates in an array structure
85                        if(this.returnIsoRanges){
86                                datesWithRanges = this._returnDatesWithIsoRanges(this._sort());
87                                return datesWithRanges;
88                        }else{
89                                return this._sort();
90                        }
91                },
92               
93                _setValueAttr: function(/*Date|Number|array*/ value, /*Boolean*/ priorityChange){
94                        // summary:
95                        //              Support set("value", ...)
96                        // description:
97                        //              Set the passed dates to the selected date and updates the value of this widget
98                        //              to reflect that
99                        // value:
100                        //              Can be a Date, the number of milliseconds since 1970 or an array of ISO dates (['2011-07-01', '2001-06-01']).
101                        // tags:
102                        //      protected
103                       
104                        //If we are passed an array of ISO dates, we are going to mark each date in the list as selected
105                        //We perform the normalization of the passed date
106                        this.value = {};
107                        if(dojo.isArray(value)) {
108                                dojo.forEach(value,function(element, i){
109                                        //Each element of the array could be a date or a date range
110                                        var slashPosition = element.indexOf("/");
111                                        if(slashPosition == -1){
112                                                //The element is a single date
113                                                this.value[element] = 1;
114                                        }else{
115                                                //We have a slash somewhere in the string so this is an ISO date range
116                                                var dateA=new dojo.date.stamp.fromISOString(element.substr(0,10));
117                                                var dateB=new dojo.date.stamp.fromISOString(element.substr(11,10));
118                                               
119                                                this.toggleDate(dateA,[],[]);
120                                                if((dateA - dateB) > 0){
121                                                        //We select the first date then the rest is handled as if we had selected a range
122                                                        this._addToRangeRTL(dateA, dateB, [], []);     
123                                                }else{
124                                                        //We select the first date then the rest is handled as if we had selected a range
125                                                        this._addToRangeLTR(dateA, dateB, [], []);     
126                                                }
127                                        }
128                                },this);
129                        if(value.length > 0){
130                                this.focusOnLastDate(value[value.length-1]);
131                        }
132                        }else{
133                                if(value){
134                                        // convert from Number to Date, or make copy of Date object so that setHours() call below
135                                        // doesn't affect original value
136                                        value = new this.dateClassObj(value);
137                                }
138                                if(this._isValidDate(value)){
139                                        value.setHours(1, 0, 0, 0); // round to nearest day (1am to avoid issues when DST shift occurs at midnight, see #8521, #9366)
140       
141                                        if(!this.isDisabledDate(value, this.lang)){
142                                                dateIndex = dojo.date.stamp.toISOString(value).substring(0,10);
143                                               
144                                                this.value[dateIndex] = 1;
145               
146                                                // Set focus cell to the new value.   Arguably this should only happen when there isn't a current
147                                                // focus point.   This will also repopulate the grid, showing the new selected value (and possibly
148                                                // new month/year).
149                                                this.set("currentFocus", value);
150       
151                                                if(priorityChange || typeof priorityChange == "undefined"){
152                                                        this.onChange(this.get('value'));
153                                                        this.onValueSelected(this.get('value'));        // remove in 2.0
154                                                }
155                                        }
156                                }
157                        }
158                        this._populateGrid();
159                },
160                focusOnLastDate : function(lastElement){
161                        //We put the focus on the last date so that when the user re-clicks on the calendar it will be
162                        //on the proper month
163                        var slashPositionLastDate = lastElement.indexOf("/");
164                        var dateA,dateB;
165                        if(slashPositionLastDate == -1){
166                                //This is a singleDate
167                                lastDate = lastElement;
168                        }else{
169                                dateA=new dojo.date.stamp.fromISOString(lastElement.substr(0,10));
170                                dateB=new dojo.date.stamp.fromISOString(lastElement.substr(11,10));
171                                if((dateA - dateB) > 0){
172                                        lastDate = dateA;
173                                }else{
174                                        lastDate = dateB;
175                                }
176                        }
177                        this.set("currentFocus", lastDate);             
178                },
179                _isValidDate: function(/*Date*/ value){
180                        // summary:
181                        //              Runs various tests on the value, checking that it's a valid date, rather
182                        //              than blank or NaN.
183                        // tags:
184                        //              private
185                        return value && !isNaN(value) && typeof value == "object" &&
186                                value.toString() != this.constructor.prototype.value.toString();
187                },
188                _setText: function(node, text){
189                        // summary:
190                        //              This just sets the content of node to the specified text.
191                        //              Can't do "node.innerHTML=text" because of an IE bug w/tables, see #3434.
192                        // tags:
193                        //      private
194                        while(node.firstChild){
195                                node.removeChild(node.firstChild);
196                        }
197                        node.appendChild(dojo.doc.createTextNode(text));
198                },
199
200                _populateGrid: function(){
201                        // summary:
202                        //      Fills in the calendar grid with each day (1-31)
203                        // tags:
204                        //      private
205
206                        var month = new this.dateClassObj(this.currentFocus);
207                        month.setDate(1);
208
209                        var firstDay = month.getDay(),
210                                daysInMonth = this.dateFuncObj.getDaysInMonth(month),
211                                daysInPreviousMonth = this.dateFuncObj.getDaysInMonth(this.dateFuncObj.add(month, "month", -1)),
212                                today = new this.dateClassObj(),
213                                dayOffset = dojo.cldr.supplemental.getFirstDayOfWeek(this.lang);
214                        if(dayOffset > firstDay){ dayOffset -= 7; }
215
216                        //List of all 42 displayed days in the calendar
217                        this.listOfNodes = dojo.query(".dijitCalendarDateTemplate", this.domNode);
218
219                        // Iterate through dates in the calendar and fill in date numbers and style info
220                        this.listOfNodes.forEach(function(template, i){
221                                i += dayOffset;
222                                var date = new this.dateClassObj(month),
223                                        number, clazz = "dijitCalendar", adj = 0;
224
225                                if(i < firstDay){
226                                        number = daysInPreviousMonth - firstDay + i + 1;
227                                        adj = -1;
228                                        clazz += "Previous";
229                                }else if(i >= (firstDay + daysInMonth)){
230                                        number = i - firstDay - daysInMonth + 1;
231                                        adj = 1;
232                                        clazz += "Next";
233                                }else{
234                                        number = i - firstDay + 1;
235                                        clazz += "Current";
236                                }
237
238                                if(adj){
239                                        date = this.dateFuncObj.add(date, "month", adj);
240                                }
241                                date.setDate(number);
242
243                                if(!this.dateFuncObj.compare(date, today, "date")){
244                                        clazz = "dijitCalendarCurrentDate " + clazz;
245                                }
246
247                                //If the date falls outside of the min or max constraints, we do nothing
248                                dateIndex = dojo.date.stamp.toISOString(date).substring(0,10);
249                               
250                                if(!this.isDisabledDate(date, this.lang)){
251                                        //If the node is already selected, the user clicking on it once more will deselect it
252                                        //so we will destroy it in the value object. If the date was not previously selected
253                                        //The user wants to select it so we add it to the value object
254                                        if(this._isSelectedDate(date, this.lang)){
255                                                if(this.value[dateIndex]){
256                                                        clazz = "dijitCalendarSelectedDate " + clazz;
257                                                }else{
258                                                        clazz = clazz.replace("dijitCalendarSelectedDate ","");
259                                                }
260                                        }
261                                }
262                                if(this._isSelectedDate(date, this.lang)){
263                                        clazz = "dijitCalendarBrowsingDate " + clazz;
264                                }
265
266                                if(this.isDisabledDate(date, this.lang)){
267                                        clazz = "dijitCalendarDisabledDate " + clazz;
268                                }
269
270                                var clazz2 = this.getClassForDate(date, this.lang);
271                                if(clazz2){
272                                        clazz = clazz2 + " " + clazz;
273                                }
274
275                                template.className = clazz + "Month dijitCalendarDateTemplate";
276                                template.dijitDateValue = date.valueOf();                               // original code
277                                dojo.attr(template, "dijitDateValue", date.valueOf());  // so I can dojo.query() it
278                                var label = dojo.query(".dijitCalendarDateLabel", template)[0],
279                                        text = date.getDateLocalized ? date.getDateLocalized(this.lang) : date.getDate();
280                                this._setText(label, text);
281                        }, this);
282
283                        // Repopulate month drop down list based on current year.
284                        // Need to do this to hide leap months in Hebrew calendar.
285                        var monthNames = this.dateLocaleModule.getNames('months', 'wide', 'standAlone', this.lang, month);
286                        this.monthDropDownButton.dropDown.set("months", monthNames);
287
288                        // Set name of current month and also fill in spacer element with all the month names
289                        // (invisible) so that the maximum width will affect layout.   But not on IE6 because then
290                        // the center <TH> overlaps the right <TH> (due to a browser bug).
291                        this.monthDropDownButton.containerNode.innerHTML =
292                                (dojo.isIE == 6 ? "" : "<div class='dijitSpacer'>" + this.monthDropDownButton.dropDown.domNode.innerHTML + "</div>") +
293                                "<div class='dijitCalendarMonthLabel dijitCalendarCurrentMonthLabel'>" +  monthNames[month.getMonth()] + "</div>";
294
295                        // Fill in localized prev/current/next years
296                        var y = month.getFullYear() - 1;
297                        var d = new this.dateClassObj();
298                        dojo.forEach(["previous", "current", "next"], function(name){
299                                d.setFullYear(y++);
300                                this._setText(this[name+"YearLabelNode"],
301                                        this.dateLocaleModule.format(d, {selector:'year', locale:this.lang}));
302                        }, this);
303                },
304
305                goToToday: function(){
306                        // summary:
307                        //      We go to today but we do no select it
308                        this.set('currentFocus', new this.dateClassObj(), false);
309                },
310
311                constructor: function(/*Object*/args){
312                        var dateClass = (args.datePackage && (args.datePackage != "dojo.date"))? args.datePackage + ".Date" : "Date";
313                        this.dateClassObj = dojo.getObject(dateClass, false);
314                        this.datePackage = args.datePackage || this.datePackage;
315                        this.dateFuncObj = dojo.getObject(this.datePackage, false);
316                        this.dateLocaleModule = dojo.getObject(this.datePackage + ".locale", false);
317                },
318
319                buildRendering: function(){
320                        this.inherited(arguments);
321                        dojo.setSelectable(this.domNode, false);
322
323                        var cloneClass = dojo.hitch(this, function(clazz, n){
324                                var template = dojo.query(clazz, this.domNode)[0];
325                                for(var i=0; i<n; i++){
326                                        template.parentNode.appendChild(template.cloneNode(true));
327                                }
328                        });
329
330                        // clone the day label and calendar day templates 6 times to make 7 columns
331                        cloneClass(".dijitCalendarDayLabelTemplate", 6);
332                        cloneClass(".dijitCalendarDateTemplate", 6);
333
334                        // now make 6 week rows
335                        cloneClass(".dijitCalendarWeekTemplate", 5);
336
337                        // insert localized day names in the header
338                        var dayNames = this.dateLocaleModule.getNames('days', this.dayWidth, 'standAlone', this.lang);
339                        var dayOffset = dojo.cldr.supplemental.getFirstDayOfWeek(this.lang);
340                        dojo.query(".dijitCalendarDayLabel", this.domNode).forEach(function(label, i){
341                                this._setText(label, dayNames[(i + dayOffset) % 7]);
342                        }, this);
343
344                        var dateObj = new this.dateClassObj(this.currentFocus);
345
346                        this.monthDropDownButton.dropDown = new dojox.widget._MonthDropDown({
347                                id: this.id + "_mdd",
348                                onChange: dojo.hitch(this, "_onMonthSelect")
349                        });
350
351                        this.set('currentFocus', dateObj, false);       // draw the grid to the month specified by currentFocus
352
353                        // Set up repeating mouse behavior for increment/decrement of months/years
354                        var _this = this;
355                        var typematic = function(nodeProp, dateProp, adj){
356                                _this._connects.push(
357                                        dijit.typematic.addMouseListener(_this[nodeProp], _this, function(count){
358                                                if(count >= 0){ _this._adjustDisplay(dateProp, adj); }
359                                        }, 0.8, 500)
360                                );
361                        };
362                        typematic("incrementMonth", "month", 1);
363                        typematic("decrementMonth", "month", -1);
364                        typematic("nextYearLabelNode", "year", 1);
365                        typematic("previousYearLabelNode", "year", -1);
366                },
367
368                _adjustDisplay: function(/*String*/ part, /*int*/ amount){
369                        // summary:
370                        //      Moves calendar forwards or backwards by months or years
371                        // part:
372                        //      "month" or "year"
373                        // amount:
374                        //      Number of months or years
375                        // tags:
376                        //      private
377                        this._setCurrentFocusAttr(this.dateFuncObj.add(this.currentFocus, part, amount));
378                },
379
380                _setCurrentFocusAttr: function(/*Date*/ date, /*Boolean*/ forceFocus){
381                        // summary:
382                        //              If the calendar currently has focus, then focuses specified date,
383                        //              changing the currently displayed month/year if necessary.
384                        //              If the calendar doesn't have focus, updates currently
385                        //              displayed month/year, and sets the cell that will get focus.
386                        // forceFocus:
387                        //              If true, will focus() the cell even if calendar itself doesn't have focus
388
389                        var oldFocus = this.currentFocus,
390                                oldCell = oldFocus ? dojo.query("[dijitDateValue=" + oldFocus.valueOf() + "]", this.domNode)[0] : null;
391
392                        // round specified value to nearest day (1am to avoid issues when DST shift occurs at midnight, see #8521, #9366)
393                        date = new this.dateClassObj(date);
394                        date.setHours(1, 0, 0, 0);
395
396                        this._set("currentFocus", date);
397                        var currentMonth = dojo.date.stamp.toISOString(date).substring(0,7);
398                        //We only redraw the grid if we're in a new month
399                        if(currentMonth != this.previousMonth){
400                                this._populateGrid();
401                                this.previousMonth = currentMonth;
402                        }
403
404                        // set tabIndex=0 on new cell, and focus it (but only if Calendar itself is focused)
405                        var newCell = dojo.query("[dijitDateValue=" + date.valueOf() + "]", this.domNode)[0];
406                        newCell.setAttribute("tabIndex", this.tabIndex);
407                        if(this._focused || forceFocus){
408                                newCell.focus();
409                        }
410
411                        // set tabIndex=-1 on old focusable cell
412                        if(oldCell && oldCell != newCell){
413                                if(dojo.isWebKit){      // see #11064 about webkit bug
414                                        oldCell.setAttribute("tabIndex", "-1");
415                                }else{
416                                                oldCell.removeAttribute("tabIndex");                           
417                                }
418                        }
419                },
420
421                focus: function(){
422                        // summary:
423                        //              Focus the calendar by focusing one of the calendar cells
424                        this._setCurrentFocusAttr(this.currentFocus, true);
425                },
426
427                _onMonthSelect: function(/*Number*/ newMonth){
428                        // summary:
429                        //      Handler for when user selects a month from the drop down list
430                        // tags:
431                        //      protected
432
433                        // move to selected month, bounding by the number of days in the month
434                        // (ex: dec 31 --> jan 28, not jan 31)
435                        this.currentFocus = this.dateFuncObj.add(this.currentFocus, "month",
436                                newMonth - this.currentFocus.getMonth());
437                        this._populateGrid();
438                },
439               
440                toggleDate : function(/*date*/ dateToToggle, /*array of dates*/ selectedDates, /*array of dates*/ unselectedDates){
441                       
442                        //Obtain CSS class before toggling if necessary
443                        var dateIndex = dojo.date.stamp.toISOString(dateToToggle).substring(0,10);                       
444                        //If previously selected we unselect and vice-versa
445                        if(this.value[dateIndex]){
446                                this.unselectDate(dateToToggle, unselectedDates);                       
447                        }else{
448                                this.selectDate(dateToToggle, selectedDates);
449                        }               
450                },
451               
452                selectDate : function(/*date*/ dateToSelect, /*array of dates*/ selectedDates){
453                        //Selects the passed iso date, changes its class and records it in the selected dates array
454                        var node = this._getNodeByDate(dateToSelect);
455                        var clazz = node.className;
456                        var dateIndex = dojo.date.stamp.toISOString(dateToSelect).substring(0,10);
457                        this.value[dateIndex] = 1;
458                        selectedDates.push(dateIndex);                 
459                        clazz = "dijitCalendarSelectedDate " + clazz;
460                        //We update CSS class
461                        node.className = clazz;
462                },
463               
464                unselectDate : function(/*date*/ dateToUnselect, /*array of dates*/ unselectedDates){
465                        //Unselects the passed iso date, changes its class and records it in the unselected dates array
466                        var node = this._getNodeByDate(dateToUnselect);
467                        var clazz = node.className;
468                        var dateIndex = dojo.date.stamp.toISOString(dateToUnselect).substring(0,10);
469                        delete(this.value[dateIndex]);
470                        unselectedDates.push(dateIndex);
471                        clazz = clazz.replace("dijitCalendarSelectedDate ","");
472                        //We update CSS class
473                        node.className = clazz;
474                },
475
476                _getNodeByDate : function(/*ISO date*/ dateNode){
477                        //return the node that corresponds to the passed ISO date
478                        var firstDate = new this.dateClassObj(this.listOfNodes[0].dijitDateValue);
479                        var difference = Math.abs(dojo.date.difference(firstDate, dateNode, "day"));
480                        return this.listOfNodes[difference];
481                },
482
483                _onDayClick: function(/*Event*/ evt){
484                        // summary:
485                        //      Handler for day clicks, selects the date if appropriate
486                        // tags:
487                        //      protected
488                       
489                        //If we coming out of selecting a range, we need to skip this onDayClick or else we
490                        //are going to deselect a date that has just been selected or deselect one that just was
491                        //selected
492                                dojo.stopEvent(evt);
493                                for(var node = evt.target; node && !node.dijitDateValue; node = node.parentNode);
494                                if(node && !dojo.hasClass(node, "dijitCalendarDisabledDate")){
495                                        value = new this.dateClassObj(node.dijitDateValue);
496                                        if(!this.rangeJustSelected){
497                                                this.toggleDate(value,[],[]);
498                                                //To record the date that was selected prior to the one currently selected
499                                                //needed in the event we are selecting a range of dates
500                                                this.previouslySelectedDay = value;
501                                                this.set("currentFocus", value);
502                                                this.onValueSelected([dojo.date.stamp.toISOString(value).substring(0,10)]);
503                                               
504                                        }else{
505                                                this.rangeJustSelected = false;
506                                                this.set("currentFocus", value);
507                                        }
508                                }
509                },
510
511                _onDayMouseOver: function(/*Event*/ evt){
512                        // summary:
513                        //      Handler for mouse over events on days, sets hovered style
514                        // tags:
515                        //      protected
516
517                        // event can occur on <td> or the <span> inside the td,
518                        // set node to the <td>.
519                        var node =
520                                dojo.hasClass(evt.target, "dijitCalendarDateLabel") ?
521                                evt.target.parentNode :
522                                evt.target;
523
524                        if(node && (node.dijitDateValue || node == this.previousYearLabelNode || node == this.nextYearLabelNode) ){
525                                dojo.addClass(node, "dijitCalendarHoveredDate");
526                                this._currentNode = node;
527                        }
528                },
529                _setEndRangeAttr: function(/*Date*/ value){
530                        // description:
531                        //              records the end of a date range
532                        // tags:
533                        //      protected
534                        value = new this.dateClassObj(value);
535                        value.setHours(1); // to avoid issues when DST shift occurs at midnight, see #8521, #9366
536                        this.endRange = value;
537                },
538                _getEndRangeAttr: function(){
539                //              Returns the EndRange date that is set when selecting a range
540                        var value = new this.dateClassObj(this.endRange);
541                        value.setHours(0, 0, 0, 0); // return midnight, local time for back-compat
542               
543                        // If daylight savings pushes midnight to the previous date, fix the Date
544                        // object to point at 1am so it will represent the correct day. See #9366
545                        if(value.getDate() < this.endRange.getDate()){
546                                value = this.dateFuncObj.add(value, "hour", 1);
547                        }
548                        return value;
549                },
550
551                _onDayMouseOut: function(/*Event*/ evt){
552                        // summary:
553                        //      Handler for mouse out events on days, clears hovered style
554                        // tags:
555                        //      protected
556       
557                        if(!this._currentNode){ return; }
558                       
559                        // if mouse out occurs moving from <td> to <span> inside <td>, ignore it
560                        if(evt.relatedTarget && evt.relatedTarget.parentNode == this._currentNode){ return; }
561                        var cls = "dijitCalendarHoveredDate";
562                        if(dojo.hasClass(this._currentNode, "dijitCalendarActiveDate")) {
563                                cls += " dijitCalendarActiveDate";
564                        }
565                        dojo.removeClass(this._currentNode, cls);
566                        this._currentNode = null;
567                },
568                _onDayMouseDown: function(/*Event*/ evt){
569                        var node = evt.target.parentNode;
570                        if(node && node.dijitDateValue){
571                                dojo.addClass(node, "dijitCalendarActiveDate");
572                                this._currentNode = node;
573                        }
574                        //if shift is pressed, we know the user is selecting a range,
575                        //in which case we are going to select a range of date
576                        if(evt.shiftKey && this.previouslySelectedDay){
577                                //necessary to know whether or not we are in the process of selecting a range of dates 
578                                this.selectingRange = true;
579                                this.set('endRange', node.dijitDateValue);
580                                this._selectRange();
581                        }else{
582                                this.selectingRange = false;
583                                this.previousRangeStart = null;
584                                this.previousRangeEnd = null;
585                        }
586                },
587               
588                _onDayMouseUp: function(/*Event*/ evt){
589                        var node = evt.target.parentNode;
590                        if(node && node.dijitDateValue){
591                                dojo.removeClass(node, "dijitCalendarActiveDate");
592                        }
593                },
594
595//TODO: use typematic
596                handleKey: function(/*Event*/ evt){
597                        // summary:
598                        //              Provides keyboard navigation of calendar.
599                        // description:
600                        //              Called from _onKeyPress() to handle keypress on a stand alone Calendar,
601                        //              and also from `dijit.form._DateTimeTextBox` to pass a keypress event
602                        //              from the `dijit.form.DateTextBox` to be handled in this widget
603                        // returns:
604                        //              False if the key was recognized as a navigation key,
605                        //              to indicate that the event was handled by Calendar and shouldn't be propogated
606                        // tags:
607                        //              protected
608                        var dk = dojo.keys,
609                                increment = -1,
610                                interval,
611                                newValue = this.currentFocus;
612                        switch(evt.keyCode){
613                                case dk.RIGHT_ARROW:
614                                        increment = 1;
615                                        //fallthrough...
616                                case dk.LEFT_ARROW:
617                                        interval = "day";
618                                        if(!this.isLeftToRight()){ increment *= -1; }
619                                        break;
620                                case dk.DOWN_ARROW:
621                                        increment = 1;
622                                        //fallthrough...
623                                case dk.UP_ARROW:
624                                        interval = "week";
625                                        break;
626                                case dk.PAGE_DOWN:
627                                        increment = 1;
628                                        //fallthrough...
629                                case dk.PAGE_UP:
630                                        interval = evt.ctrlKey || evt.altKey ? "year" : "month";
631                                        break;
632                                case dk.END:
633                                        // go to the next month
634                                        newValue = this.dateFuncObj.add(newValue, "month", 1);
635                                        // subtract a day from the result when we're done
636                                        interval = "day";
637                                        //fallthrough...
638                                case dk.HOME:
639                                        newValue = new this.dateClassObj(newValue);
640                                        newValue.setDate(1);
641                                        break;
642                                case dk.ENTER:
643                                case dk.SPACE:
644                                        if(evt.shiftKey && this.previouslySelectedDay){
645                                                this.selectingRange = true;
646                                                this.set('endRange', newValue);
647                                                this._selectRange();
648                                        }else{
649                                                this.selectingRange = false;                           
650                                                this.toggleDate(newValue,[],[]);
651                                                //We record the selected date as the previous one
652                                                //In case we are selecting the first date of a range
653                                                this.previouslySelectedDay = newValue;
654                                                this.previousRangeStart = null;
655                                                this.previousRangeEnd = null;
656                                                this.onValueSelected([dojo.date.stamp.toISOString(newValue).substring(0,10)]);
657                                               
658                                        }
659                                        break;
660                                default:
661                                        return true;
662                        }
663
664                        if(interval){
665                                newValue = this.dateFuncObj.add(newValue, interval, increment);
666                        }
667
668                        this.set("currentFocus", newValue);
669
670                        return false;
671                },
672
673                _onKeyPress: function(/*Event*/ evt){
674                        // summary:
675                        //              For handling keypress events on a stand alone calendar
676                        if(!this.handleKey(evt)){
677                                dojo.stopEvent(evt);
678                        }
679                },
680               
681                _removeFromRangeLTR : function(/*date*/ beginning, /*date*/ end, /*array*/selectedDates, /*array*/unselectedDates){
682        //In this method we remove some dates from a range from left to right
683                        difference = Math.abs(dojo.date.difference(beginning, end, "day"));
684                        for(var i = 0; i <= difference; i++){
685                                var nextDay = dojo.date.add(beginning, 'day',i);
686                                this.toggleDate(nextDay, selectedDates, unselectedDates);
687                        }
688                        if(this.previousRangeEnd == null){
689                                //necessary to keep track of the previous range's end date
690                                this.previousRangeEnd = end;
691                        }else{
692                                if(dojo.date.compare(end, this.previousRangeEnd, 'date') > 0 )
693                                        this.previousRangeEnd = end;
694                        }
695                        if(this.previousRangeStart == null){
696                                //necessary to keep track of the previous range's start date
697                                this.previousRangeStart = end;
698                        }else{
699                                if(dojo.date.compare(end, this.previousRangeStart, 'date') > 0 )
700                                        this.previousRangeStart = end;
701                        }
702                        this.previouslySelectedDay = dojo.date.add(nextDay, 'day',1);   
703                },
704                _removeFromRangeRTL : function(/*date*/ beginning, /*date*/ end, /*array*/selectedDates, /*array*/unselectedDates){
705                        //If the end of the range is earlier than the beginning (back in time),
706                        //we are going to start from the end and move backward
707       
708                        difference = Math.abs(dojo.date.difference(beginning, end, "day"));
709                        for(var i = 0; i <= difference; i++){
710                                var nextDay = dojo.date.add(beginning, 'day',-i);
711                                this.toggleDate(nextDay, selectedDates, unselectedDates);
712                        }
713                        if(this.previousRangeEnd == null){
714                                this.previousRangeEnd = end;
715                        }else{
716                                if(dojo.date.compare(end, this.previousRangeEnd, 'date') < 0 ){
717                                        this.previousRangeEnd = end;
718                                }
719                        }
720                        if(this.previousRangeStart == null){
721                                this.previousRangeStart = end;
722                        }else{
723                                if(dojo.date.compare(end, this.previousRangeStart, 'date') < 0 ){
724                                        this.previousRangeStart = end;
725                                }
726                        }
727                        this.previouslySelectedDay = dojo.date.add(nextDay, 'day',-1);
728                },
729                _addToRangeRTL : function(/*date*/ beginning, /*date*/ end, /*array*/selectedDates, /*array*/unselectedDates){
730               
731                        difference = Math.abs(dojo.date.difference(beginning, end, "day"));
732                        //If the end of the range is earlier than the beginning (back in time),
733                        //we are going to start from the end and move backward
734                        for(var i = 1; i <= difference; i++){
735                                var nextDay = dojo.date.add(beginning, 'day',-i);
736                                this.toggleDate(nextDay, selectedDates, unselectedDates);
737                        }
738       
739                        if(this.previousRangeStart == null){
740                                this.previousRangeStart = end;
741                        }else{
742                                if(dojo.date.compare(end, this.previousRangeStart, 'date') < 0 ){
743                                        this.previousRangeStart = end;
744                                }
745                        }
746                        if(this.previousRangeEnd == null){
747                                this.previousRangeEnd = beginning;
748                        }else{
749                                if(dojo.date.compare(beginning, this.previousRangeEnd, 'date') > 0 ){
750                                        this.previousRangeEnd = beginning;
751                                }
752                        }
753                        this.previouslySelectedDay = nextDay;
754                },
755                _addToRangeLTR : function(/*date*/ beginning, /*date*/ end, /*array*/selectedDates, /*array*/unselectedDates){
756                        //If the end of the range is later than the beginning,
757                        //adding dates from left to right
758                        difference = Math.abs(dojo.date.difference(beginning, end, "day"));
759                        for(var i = 1; i <= difference; i++){
760                                var nextDay = dojo.date.add(beginning, 'day',i);
761                                this.toggleDate(nextDay, selectedDates, unselectedDates);
762                        }
763                        if(this.previousRangeStart == null){
764                                this.previousRangeStart = beginning;
765                        }else{
766                                if(dojo.date.compare(beginning, this.previousRangeStart, 'date') < 0 ){
767                                        this.previousRangeStart = beginning;
768                                }
769                        }
770                        if(this.previousRangeEnd == null){
771                                this.previousRangeEnd = end;
772                        }else{
773                                if(dojo.date.compare(end, this.previousRangeEnd, 'date') > 0 ){
774                                        this.previousRangeEnd = end;
775                                }
776                        }
777                        this.previouslySelectedDay = nextDay;
778                },
779                _selectRange : function(){
780                        //This method will toggle the dates in the selected range.
781                        var selectedDates = []; //Will gather the list of ISO dates that are selected
782                        var unselectedDates = []; //Will gather the list of ISO dates that are unselected
783                        var beginning = this.previouslySelectedDay;
784                        var end = this.get('endRange');
785                       
786                        if(!this.previousRangeStart && !this.previousRangeEnd){
787                                removingFromRange = false;
788                        }else{
789                                if((dojo.date.compare(end, this.previousRangeStart, 'date') < 0) || (dojo.date.compare(end, this.previousRangeEnd, 'date') > 0)){
790                                //We are adding to range
791                                        removingFromRange = false;
792                                }else{// Otherwise we are removing from the range
793                                        removingFromRange = true;
794                                }
795                        }
796                        if(removingFromRange == true){
797                                if(dojo.date.compare(end, beginning, 'date') < 0){
798                                        //We are removing from the range, starting from the end (Right to left)
799                                        this._removeFromRangeRTL(beginning, end, selectedDates, unselectedDates);
800                                }else{
801                                //The end of the range is later in time than the beginning: We go from left to right
802                                        this._removeFromRangeLTR(beginning, end, selectedDates, unselectedDates);
803                                }
804                        }else{
805                                //We are adding to the range
806                                if(dojo.date.compare(end, beginning, 'date') < 0){
807                                        this._addToRangeRTL(beginning, end, selectedDates, unselectedDates);
808                                }else{
809                                        this._addToRangeLTR(beginning, end, selectedDates, unselectedDates);
810                                }
811                        }
812                        //We call the extension point with the changed dates
813                        if(selectedDates.length > 0){
814                                this.onValueSelected(selectedDates);
815                        }
816                        if(unselectedDates.length > 0){
817                                this.onValueUnselected(unselectedDates);
818                        }
819                        this.rangeJustSelected = true; //Indicates that we just selected a range.
820                },
821
822                onValueSelected: function(/*array of ISO dates*/ dates){
823                        // summary:
824                        //              Notification that a date cell or more were selected.
825                        // description:
826                        //      Passes on the list of ISO dates that are selected
827                        // tags:
828                        //      protected
829                },
830
831                onValueUnselected: function(/*array of ISO dates*/ dates){
832                        // summary:
833                        //              Notification that a date cell or more were unselected.
834                        // description:
835                        //      Passes on the list of ISO dates that are unselected
836                        // tags:
837                        //      protected
838                },
839                onChange: function(/*Date*/ date){
840                        // summary:
841                        //              Called only when the selected date has changed
842                },
843
844                _isSelectedDate: function(/*Date*/ dateObject, /*String?*/ locale){
845                        // summary:
846                        //              Returns true if the passed date is part of the selected dates of the calendar
847                       
848                                dateIndex = dojo.date.stamp.toISOString(dateObject).substring(0,10);
849                                return this.value[dateIndex];
850                },
851
852                isDisabledDate: function(/*Date*/ dateObject, /*String?*/ locale){
853                        // summary:
854                        //              May be overridden to disable certain dates in the calendar e.g. `isDisabledDate=dojo.date.locale.isWeekend`
855                        // tags:
856                        //      extension
857/*=====
858                        return false; // Boolean
859=====*/
860                },
861
862                getClassForDate: function(/*Date*/ dateObject, /*String?*/ locale){
863                        // summary:
864                        //              May be overridden to return CSS classes to associate with the date entry for the given dateObject,
865                        //              for example to indicate a holiday in specified locale.
866                        // tags:
867                        //      extension
868
869/*=====
870                        return ""; // String
871=====*/
872                },
873                _sort : function(){
874                        //This function returns a sorted version of the value array that represents the selected dates.
875                        if(this.value == {}){return [];}
876                        //We create an array of date objects with the dates that were selected by the user.
877                        var selectedDates = [];
878                        for (var selDate in this.value){
879                                selectedDates.push(selDate);
880                        }
881                        //Actual sorting
882                        selectedDates.sort(function(a, b){
883                                var dateA=new Date(a), dateB=new Date(b);
884                                return dateA-dateB;
885                        });
886                        return selectedDates;
887                },
888                _returnDatesWithIsoRanges : function(selectedDates /*Array of sorted ISO dates*/){
889                //this method receives a sorted array of dates and returns an array of dates and date ranges where
890                //such range exist. For instance when passed with selectedDates = ['2010-06-14', '2010-06-15', '2010-12-25']
891                //it would return [2010-06-14/2010-06-15,  '2010-12-25']
892                var returnDates = [];
893                if(selectedDates.length > 1){
894                        //initialisation
895                        var weHaveRange = false,
896                                rangeCount = 0,
897                                startRange = null,
898                                lastDayRange = null,
899                                previousDate = dojo.date.stamp.fromISOString(selectedDates[0]);
900                       
901                        for(var i = 1; i < selectedDates.length+1; i++){
902                                currentDate = dojo.date.stamp.fromISOString(selectedDates[i]);
903                                if(weHaveRange){
904                                //We are in the middle of a range                               
905                                        difference = Math.abs(dojo.date.difference(previousDate, currentDate, "day"));
906                                        if(difference == 1){
907                                                //we continue with the range
908                                                lastDayRange = currentDate;
909                                        }else{
910                                                //end of the range, reset variables for maybe the next range..
911                                                range = dojo.date.stamp.toISOString(startRange).substring(0,10)
912                                                                + "/" + dojo.date.stamp.toISOString(lastDayRange).substring(0,10);
913                                                returnDates.push(range);
914                                                weHaveRange = false;
915                                        }
916                                }else{
917                                        //We are not in a range to begin with
918                                        difference = Math.abs(dojo.date.difference(previousDate, currentDate, "day"));
919                                        if(difference == 1){
920                                                //These are two consecutive dates: This is a range!
921                                                weHaveRange = true;
922                                                startRange = previousDate;
923                                                lastDayRange = currentDate;
924                                        }else{
925                                                //this is a standalone date
926                                                returnDates.push(dojo.date.stamp.toISOString(previousDate).substring(0,10));
927                                        }
928                                }
929                                previousDate = currentDate;
930                        }
931                        return returnDates;
932                }else{
933                        //If there's only one selected date we return only it
934                                return selectedDates;
935                        }
936                }
937        }       
938);
939
940//FIXME: can we use dijit.Calendar._MonthDropDown directly?
941dojo.declare("dojox.widget._MonthDropDown", [dijit._Widget, dijit._TemplatedMixin, dijit._WidgetsInTemplateMixin], {
942        // summary:
943        //              The month drop down
944
945        // months: String[]
946        //              List of names of months, possibly w/some undefined entries for Hebrew leap months
947        //              (ex: ["January", "February", undefined, "April", ...])
948        months: [],
949
950        templateString: "<div class='dijitCalendarMonthMenu dijitMenu' " +
951                "dojoAttachEvent='onclick:_onClick,onmouseover:_onMenuHover,onmouseout:_onMenuHover'></div>",
952
953        _setMonthsAttr: function(/*String[]*/ months){
954                this.domNode.innerHTML = dojo.map(months, function(month, idx){
955                                return month ? "<div class='dijitCalendarMonthLabel' month='" + idx +"'>" + month + "</div>" : "";
956                        }).join("");
957        },
958
959        _onClick: function(/*Event*/ evt){
960                this.onChange(dojo.attr(evt.target, "month"));
961        },
962
963        onChange: function(/*Number*/ month){
964                // summary:
965                //              Callback when month is selected from drop down
966        },
967
968        _onMenuHover: function(evt){
969                dojo.toggleClass(evt.target, "dijitCalendarMonthLabelHover", evt.type == "mouseover");
970        }
971});
972
973return dojox.widget.MultiSelectCalendar;
974});
Note: See TracBrowser for help on using the repository browser.