source: Dev/branches/rest-dojo-ui/client/dijit/_TimePicker.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: 16.8 KB
Line 
1define([
2        "dojo/_base/array", // array.forEach
3        "dojo/date", // date.compare
4        "dojo/date/locale", // locale.format
5        "dojo/date/stamp", // stamp.fromISOString stamp.toISOString
6        "dojo/_base/declare", // declare
7        "dojo/dom-class", // domClass.add domClass.contains domClass.toggle
8        "dojo/dom-construct", // domConstruct.create
9        "dojo/_base/event", // event.stop
10        "dojo/_base/kernel", // deprecated
11        "dojo/keys", // keys
12        "dojo/_base/lang", // lang.mixin
13        "dojo/_base/sniff", // has("ie")
14        "dojo/query", // query
15        "dijit/typematic",
16        "./_Widget",
17        "./_TemplatedMixin",
18        "./form/_FormValueWidget",
19        "dojo/text!./templates/TimePicker.html"
20], function(array, ddate, locale, stamp, declare, domClass, domConstruct, event, kernel, keys, lang, has, query,
21                        typematic, _Widget, _TemplatedMixin, _FormValueWidget, template){
22
23/*=====
24        var _Widget = dijit._Widget;
25        var _TemplatedMixin = dijit._TemplatedMixin;
26        var _FormValueWidget = dijit.form._FormValueWidget;
27=====*/
28
29        // module:
30        //              dijit/_TimePicker
31        // summary:
32        //              A graphical time picker.
33
34
35        /*=====
36        declare(
37                "dijit._TimePicker.__Constraints",
38                locale.__FormatOptions,
39                {
40                        // clickableIncrement: String
41                        //              See `dijit._TimePicker.clickableIncrement`
42                        clickableIncrement: "T00:15:00",
43
44                        // visibleIncrement: String
45                        //              See `dijit._TimePicker.visibleIncrement`
46                        visibleIncrement: "T01:00:00",
47
48                        // visibleRange: String
49                        //              See `dijit._TimePicker.visibleRange`
50                        visibleRange: "T05:00:00"
51                }
52        );
53        =====*/
54
55        return declare("dijit._TimePicker", [_Widget, _TemplatedMixin], {
56                // summary:
57                //              A graphical time picker.
58                //              This widget is used internally by other widgets and is not available
59                //              as a standalone widget due to lack of accessibility support.
60
61                templateString: template,
62
63                // baseClass: [protected] String
64                //              The root className to use for the various states of this widget
65                baseClass: "dijitTimePicker",
66
67                // clickableIncrement: String
68                //              ISO-8601 string representing the amount by which
69                //              every clickable element in the time picker increases.
70                //              Set in local time, without a time zone.
71                //              Example: `T00:15:00` creates 15 minute increments
72                //              Must divide dijit._TimePicker.visibleIncrement evenly
73                clickableIncrement: "T00:15:00",
74
75                // visibleIncrement: String
76                //              ISO-8601 string representing the amount by which
77                //              every element with a visible time in the time picker increases.
78                //              Set in local time, without a time zone.
79                //              Example: `T01:00:00` creates text in every 1 hour increment
80                visibleIncrement: "T01:00:00",
81
82                // visibleRange: String
83                //              ISO-8601 string representing the range of this TimePicker.
84                //              The TimePicker will only display times in this range.
85                //              Example: `T05:00:00` displays 5 hours of options
86                visibleRange: "T05:00:00",
87
88                // value: String
89                //              Date to display.
90                //              Defaults to current time and date.
91                //              Can be a Date object or an ISO-8601 string.
92                //              If you specify the GMT time zone (`-01:00`),
93                //              the time will be converted to the local time in the local time zone.
94                //              Otherwise, the time is considered to be in the local time zone.
95                //              If you specify the date and isDate is true, the date is used.
96                //              Example: if your local time zone is `GMT -05:00`,
97                //              `T10:00:00` becomes `T10:00:00-05:00` (considered to be local time),
98                //              `T10:00:00-01:00` becomes `T06:00:00-05:00` (4 hour difference),
99                //              `T10:00:00Z` becomes `T05:00:00-05:00` (5 hour difference between Zulu and local time)
100                //              `yyyy-mm-ddThh:mm:ss` is the format to set the date and time
101                //              Example: `2007-06-01T09:00:00`
102                value: new Date(),
103
104                _visibleIncrement:2,
105                _clickableIncrement:1,
106                _totalIncrements:10,
107
108                // constraints: dijit._TimePicker.__Constraints
109                //              Specifies valid range of times (start time, end time)
110                constraints:{},
111
112/*=====
113                serialize: function(val, options){
114                        // summary:
115                        //              User overridable function used to convert the attr('value') result to a String
116                        // val: Date
117                        //              The current value
118                        // options: Object?
119                        // tags:
120                        //              protected
121                },
122=====*/
123                serialize: stamp.toISOString,
124
125/*=====
126                // filterString: string
127                //              The string to filter by
128                filterString: "",
129=====*/
130
131                setValue: function(/*Date*/ value){
132                        // summary:
133                        //              Deprecated.  Used set('value') instead.
134                        // tags:
135                        //              deprecated
136                        kernel.deprecated("dijit._TimePicker:setValue() is deprecated.  Use set('value', ...) instead.", "", "2.0");
137                        this.set('value', value);
138                },
139
140                _setValueAttr: function(/*Date*/ date){
141                        // summary:
142                        //              Hook so set('value', ...) works.
143                        // description:
144                        //              Set the value of the TimePicker.
145                        //              Redraws the TimePicker around the new date.
146                        // tags:
147                        //              protected
148                        this._set("value", date);
149                        this._showText();
150                },
151
152                _setFilterStringAttr: function(val){
153                        // summary:
154                        //              Called by TimeTextBox to filter the values shown in my list
155                        this._set("filterString", val);
156                        this._showText();
157                },
158
159                isDisabledDate: function(/*===== dateObject, locale =====*/){
160                        // summary:
161                        //              May be overridden to disable certain dates in the TimePicker e.g. `isDisabledDate=locale.isWeekend`
162                        // dateObject: Date
163                        // locale: String?
164                        // type:
165                        //              extension
166                        return false; // Boolean
167                },
168
169                _getFilteredNodes: function(/*number*/ start, /*number*/ maxNum, /*Boolean*/ before, /*DOMnode*/ lastNode){
170                        // summary:
171                        //              Returns an array of nodes with the filter applied.  At most maxNum nodes
172                        //              will be returned - but fewer may be returned as well.  If the
173                        //              before parameter is set to true, then it will return the elements
174                        //              before the given index
175                        // tags:
176                        //              private
177                        var
178                                nodes = [],
179                                lastValue = lastNode ? lastNode.date : this._refDate,
180                                n,
181                                i = start,
182                                max = this._maxIncrement + Math.abs(i),
183                                chk = before ? -1 : 1,
184                                dec = before ? 1 : 0,
185                                inc = 1 - dec;
186                        do{
187                                i = i - dec;
188                                n = this._createOption(i);
189                                if(n){
190                                        if((before && n.date > lastValue) || (!before && n.date < lastValue)){
191                                                break; // don't wrap
192                                        }
193                                        nodes[before ? "unshift" : "push"](n);
194                                        lastValue = n.date;
195                                }
196                                i = i + inc;
197                        }while(nodes.length < maxNum && (i*chk) < max);
198                        return nodes;
199                },
200
201                _showText: function(){
202                        // summary:
203                        //              Displays the relevant choices in the drop down list
204                        // tags:
205                        //              private
206                        var fromIso = stamp.fromISOString;
207                        this.timeMenu.innerHTML = "";
208                        this._clickableIncrementDate=fromIso(this.clickableIncrement);
209                        this._visibleIncrementDate=fromIso(this.visibleIncrement);
210                        this._visibleRangeDate=fromIso(this.visibleRange);
211                        // get the value of the increments and the range in seconds (since 00:00:00) to find out how many divs to create
212                        var
213                                sinceMidnight = function(/*Date*/ date){
214                                return date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds();
215                                },
216                                clickableIncrementSeconds = sinceMidnight(this._clickableIncrementDate),
217                                visibleIncrementSeconds = sinceMidnight(this._visibleIncrementDate),
218                                visibleRangeSeconds = sinceMidnight(this._visibleRangeDate),
219
220                        // round reference date to previous visible increment
221                                time = (this.value || this.currentFocus).getTime();
222
223                        this._refDate = new Date(time - time % (visibleIncrementSeconds*1000));
224                        this._refDate.setFullYear(1970,0,1); // match parse defaults
225
226                        // assume clickable increment is the smallest unit
227                        this._clickableIncrement = 1;
228                        // divide the visible range by the clickable increment to get the number of divs to create
229                        // example: 10:00:00/00:15:00 -> display 40 divs
230                        this._totalIncrements = visibleRangeSeconds / clickableIncrementSeconds;
231                        // divide the visible increments by the clickable increments to get how often to display the time inline
232                        // example: 01:00:00/00:15:00 -> display the time every 4 divs
233                        this._visibleIncrement = visibleIncrementSeconds / clickableIncrementSeconds;
234                        // divide the number of seconds in a day by the clickable increment in seconds to get the
235                        // absolute max number of increments.
236                        this._maxIncrement = (60 * 60 * 24) / clickableIncrementSeconds;
237
238                        var
239                                // Find the nodes we should display based on our filter.
240                                // Limit to 10 nodes displayed as a half-hearted attempt to stop drop down from overlapping <input>.
241                                after = this._getFilteredNodes(0, Math.min(this._totalIncrements >> 1, 10) - 1),
242                                before = this._getFilteredNodes(0, Math.min(this._totalIncrements, 10) - after.length, true, after[0]);
243                        array.forEach(before.concat(after), function(n){this.timeMenu.appendChild(n);}, this);
244                },
245
246                constructor: function(){
247                        this.constraints = {}; // create instance object
248                },
249
250                postMixInProperties: function(){
251                        this.inherited(arguments);
252                        this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls
253                },
254
255                _setConstraintsAttr: function(/* Object */ constraints){
256                        // brings in visibleRange, increments, etc.
257                        lang.mixin(this, constraints);
258
259                        // locale needs the lang in the constraints as locale
260                        if(!constraints.locale){
261                                constraints.locale = this.lang;
262                        }
263                },
264
265                postCreate: function(){
266                        // assign typematic mouse listeners to the arrow buttons
267                        this.connect(this.timeMenu, has("ie") ? "onmousewheel" : 'DOMMouseScroll', "_mouseWheeled");
268                        this._connects.push(typematic.addMouseListener(this.upArrow, this, "_onArrowUp", 33, 250));
269                        this._connects.push(typematic.addMouseListener(this.downArrow, this, "_onArrowDown", 33, 250));
270
271                        this.inherited(arguments);
272                },
273
274                _buttonMouse: function(/*Event*/ e){
275                        // summary:
276                        //              Handler for hover (and unhover) on up/down arrows
277                        // tags:
278                        //              private
279
280                        // in non-IE browser the "mouseenter" event will become "mouseover",
281                        // but in IE it's still "mouseenter"
282                        domClass.toggle(e.currentTarget, e.currentTarget == this.upArrow ? "dijitUpArrowHover" : "dijitDownArrowHover",
283                                e.type == "mouseenter" || e.type == "mouseover");
284                },
285
286                _createOption: function(/*Number*/ index){
287                        // summary:
288                        //              Creates a clickable time option
289                        // tags:
290                        //              private
291                        var date = new Date(this._refDate);
292                        var incrementDate = this._clickableIncrementDate;
293                        date.setHours(date.getHours() + incrementDate.getHours() * index,
294                                date.getMinutes() + incrementDate.getMinutes() * index,
295                                date.getSeconds() + incrementDate.getSeconds() * index);
296                        if(this.constraints.selector == "time"){
297                                date.setFullYear(1970,0,1); // make sure each time is for the same date
298                        }
299                        var dateString = locale.format(date, this.constraints);
300                        if(this.filterString && dateString.toLowerCase().indexOf(this.filterString) !== 0){
301                                // Doesn't match the filter - return null
302                                return null;
303                        }
304
305                        var div = domConstruct.create("div", {"class": this.baseClass+"Item"});
306                        div.date = date;
307                        div.index = index;
308                        domConstruct.create('div',{
309                                "class": this.baseClass + "ItemInner",
310                                innerHTML: dateString
311                        }, div);
312
313                        if(index%this._visibleIncrement<1 && index%this._visibleIncrement>-1){
314                                domClass.add(div, this.baseClass+"Marker");
315                        }else if(!(index%this._clickableIncrement)){
316                                domClass.add(div, this.baseClass+"Tick");
317                        }
318
319                        if(this.isDisabledDate(date)){
320                                // set disabled
321                                domClass.add(div, this.baseClass+"ItemDisabled");
322                        }
323                        if(this.value && !ddate.compare(this.value, date, this.constraints.selector)){
324                                div.selected = true;
325                                domClass.add(div, this.baseClass+"ItemSelected");
326                                if(domClass.contains(div, this.baseClass+"Marker")){
327                                        domClass.add(div, this.baseClass+"MarkerSelected");
328                                }else{
329                                        domClass.add(div, this.baseClass+"TickSelected");
330                                }
331
332                                // Initially highlight the current value.   User can change highlight by up/down arrow keys
333                                // or mouse movement.
334                                this._highlightOption(div, true);
335                        }
336                        return div;
337                },
338
339                _onOptionSelected: function(/*Object*/ tgt){
340                        // summary:
341                        //              Called when user clicks an option in the drop down list
342                        // tags:
343                        //              private
344                        var tdate = tgt.target.date || tgt.target.parentNode.date;
345                        if(!tdate || this.isDisabledDate(tdate)){ return; }
346                        this._highlighted_option = null;
347                        this.set('value', tdate);
348                        this.onChange(tdate);
349                },
350
351                onChange: function(/*Date*/ /*===== time =====*/){
352                        // summary:
353                        //              Notification that a time was selected.  It may be the same as the previous value.
354                        // tags:
355                        //      public
356                },
357
358                _highlightOption: function(/*node*/ node, /*Boolean*/ highlight){
359                        // summary:
360                        //              Turns on/off highlight effect on a node based on mouse out/over event
361                        // tags:
362                        //              private
363                        if(!node){return;}
364                        if(highlight){
365                                if(this._highlighted_option){
366                                        this._highlightOption(this._highlighted_option, false);
367                                }
368                                this._highlighted_option = node;
369                        }else if(this._highlighted_option !== node){
370                                return;
371                        }else{
372                                this._highlighted_option = null;
373                        }
374                        domClass.toggle(node, this.baseClass+"ItemHover", highlight);
375                        if(domClass.contains(node, this.baseClass+"Marker")){
376                                domClass.toggle(node, this.baseClass+"MarkerHover", highlight);
377                        }else{
378                                domClass.toggle(node, this.baseClass+"TickHover", highlight);
379                        }
380                },
381
382                onmouseover: function(/*Event*/ e){
383                        // summary:
384                        //              Handler for onmouseover event
385                        // tags:
386                        //              private
387                        this._keyboardSelected = null;
388                        var tgr = (e.target.parentNode === this.timeMenu) ? e.target : e.target.parentNode;
389                        // if we aren't targeting an item, then we return
390                        if(!domClass.contains(tgr, this.baseClass+"Item")){return;}
391                        this._highlightOption(tgr, true);
392                },
393
394                onmouseout: function(/*Event*/ e){
395                        // summary:
396                        //              Handler for onmouseout event
397                        // tags:
398                        //              private
399                        this._keyboardSelected = null;
400                        var tgr = (e.target.parentNode === this.timeMenu) ? e.target : e.target.parentNode;
401                        this._highlightOption(tgr, false);
402                },
403
404                _mouseWheeled: function(/*Event*/ e){
405                        // summary:
406                        //              Handle the mouse wheel events
407                        // tags:
408                        //              private
409                        this._keyboardSelected = null;
410                        event.stop(e);
411                        // we're not _measuring_ the scroll amount, just direction
412                        var scrollAmount = (has("ie") ? e.wheelDelta : -e.detail);
413                        this[(scrollAmount>0 ? "_onArrowUp" : "_onArrowDown")](); // yes, we're making a new dom node every time you mousewheel, or click
414                },
415
416                _onArrowUp: function(count){
417                        // summary:
418                        //              Handler for up arrow key.
419                        // description:
420                        //              Removes the bottom time and add one to the top
421                        // tags:
422                        //              private
423                        if(typeof count == "number" && count == -1){ return; } // typematic end
424                        if(!this.timeMenu.childNodes.length){ return; }
425                        var index = this.timeMenu.childNodes[0].index;
426                        var divs = this._getFilteredNodes(index, 1, true, this.timeMenu.childNodes[0]);
427                        if(divs.length){
428                                this.timeMenu.removeChild(this.timeMenu.childNodes[this.timeMenu.childNodes.length - 1]);
429                                this.timeMenu.insertBefore(divs[0], this.timeMenu.childNodes[0]);
430                        }
431                },
432
433                _onArrowDown: function(count){
434                        // summary:
435                        //              Handler for up arrow key.
436                        // description:
437                        //              Remove the top time and add one to the bottom
438                        // tags:
439                        //              private
440                        if(typeof count == "number" && count == -1){ return; } // typematic end
441                        if(!this.timeMenu.childNodes.length){ return; }
442                        var index = this.timeMenu.childNodes[this.timeMenu.childNodes.length - 1].index + 1;
443                        var divs = this._getFilteredNodes(index, 1, false, this.timeMenu.childNodes[this.timeMenu.childNodes.length - 1]);
444                        if(divs.length){
445                                this.timeMenu.removeChild(this.timeMenu.childNodes[0]);
446                                this.timeMenu.appendChild(divs[0]);
447                        }
448                },
449
450                handleKey: function(/*Event*/ e){
451                        // summary:
452                        //              Called from `dijit.form._DateTimeTextBox` to pass a keypress event
453                        //              from the `dijit.form.TimeTextBox` to be handled in this widget
454                        // tags:
455                        //              protected
456                        if(e.charOrCode == keys.DOWN_ARROW || e.charOrCode == keys.UP_ARROW){
457                                event.stop(e);
458                                // Figure out which option to highlight now and then highlight it
459                                if(this._highlighted_option && !this._highlighted_option.parentNode){
460                                        this._highlighted_option = null;
461                                }
462                                var timeMenu = this.timeMenu,
463                                        tgt = this._highlighted_option || query("." + this.baseClass + "ItemSelected", timeMenu)[0];
464                                if(!tgt){
465                                        tgt = timeMenu.childNodes[0];
466                                }else if(timeMenu.childNodes.length){
467                                        if(e.charOrCode == keys.DOWN_ARROW && !tgt.nextSibling){
468                                                this._onArrowDown();
469                                        }else if(e.charOrCode == keys.UP_ARROW && !tgt.previousSibling){
470                                                this._onArrowUp();
471                                        }
472                                        if(e.charOrCode == keys.DOWN_ARROW){
473                                                tgt = tgt.nextSibling;
474                                        }else{
475                                                tgt = tgt.previousSibling;
476                                        }
477                                }
478                                this._highlightOption(tgt, true);
479                                this._keyboardSelected = tgt;
480                                return false;
481                        }else if(e.charOrCode == keys.ENTER || e.charOrCode === keys.TAB){
482                                // mouse hover followed by TAB is NO selection
483                                if(!this._keyboardSelected && e.charOrCode === keys.TAB){
484                                        return true;    // true means don't call stopEvent()
485                                }
486
487                                // Accept the currently-highlighted option as the value
488                                if(this._highlighted_option){
489                                this._onOptionSelected({target: this._highlighted_option});
490                        }
491
492                                // Call stopEvent() for ENTER key so that form doesn't submit,
493                                // but not for TAB, so that TAB does switch focus
494                                return e.charOrCode === keys.TAB;
495                        }
496                }
497        });
498});
Note: See TracBrowser for help on using the repository browser.