1 | define([ |
---|
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/kernel", // deprecated |
---|
10 | "dojo/keys", // keys |
---|
11 | "dojo/_base/lang", // lang.mixin |
---|
12 | "dojo/sniff", // has(...) |
---|
13 | "dojo/query", // query |
---|
14 | "dojo/mouse", // mouse.wheel |
---|
15 | "dojo/on", |
---|
16 | "./_WidgetBase", |
---|
17 | "./form/_ListMouseMixin" |
---|
18 | ], function(array, ddate, locale, stamp, declare, domClass, domConstruct, kernel, keys, lang, has, query, mouse, on, |
---|
19 | _WidgetBase, _ListMouseMixin){ |
---|
20 | |
---|
21 | // module: |
---|
22 | // dijit/_TimePicker |
---|
23 | |
---|
24 | |
---|
25 | var TimePicker = declare("dijit._TimePicker", [_WidgetBase, _ListMouseMixin], { |
---|
26 | // summary: |
---|
27 | // A time picker dropdown, used by dijit/form/TimeTextBox. |
---|
28 | // This widget is not available as a standalone widget due to lack of accessibility support. |
---|
29 | |
---|
30 | // baseClass: [protected] String |
---|
31 | // The root className to use for the various states of this widget |
---|
32 | baseClass: "dijitTimePicker", |
---|
33 | |
---|
34 | // clickableIncrement: String |
---|
35 | // ISO-8601 string representing the amount by which |
---|
36 | // every clickable element in the time picker increases. |
---|
37 | // Set in local time, without a time zone. |
---|
38 | // Example: `T00:15:00` creates 15 minute increments |
---|
39 | // Must divide dijit/_TimePicker.visibleIncrement evenly |
---|
40 | clickableIncrement: "T00:15:00", |
---|
41 | |
---|
42 | // visibleIncrement: String |
---|
43 | // ISO-8601 string representing the amount by which |
---|
44 | // every element with a visible time in the time picker increases. |
---|
45 | // Set in local time, without a time zone. |
---|
46 | // Example: `T01:00:00` creates text in every 1 hour increment |
---|
47 | visibleIncrement: "T01:00:00", |
---|
48 | |
---|
49 | // value: String |
---|
50 | // Date to display. |
---|
51 | // Defaults to current time and date. |
---|
52 | // Can be a Date object or an ISO-8601 string. |
---|
53 | // If you specify the GMT time zone (`-01:00`), |
---|
54 | // the time will be converted to the local time in the local time zone. |
---|
55 | // Otherwise, the time is considered to be in the local time zone. |
---|
56 | // If you specify the date and isDate is true, the date is used. |
---|
57 | // Example: if your local time zone is `GMT -05:00`, |
---|
58 | // `T10:00:00` becomes `T10:00:00-05:00` (considered to be local time), |
---|
59 | // `T10:00:00-01:00` becomes `T06:00:00-05:00` (4 hour difference), |
---|
60 | // `T10:00:00Z` becomes `T05:00:00-05:00` (5 hour difference between Zulu and local time) |
---|
61 | // `yyyy-mm-ddThh:mm:ss` is the format to set the date and time |
---|
62 | // Example: `2007-06-01T09:00:00` |
---|
63 | value: new Date(), |
---|
64 | |
---|
65 | _visibleIncrement: 2, |
---|
66 | _clickableIncrement: 1, |
---|
67 | _totalIncrements: 10, |
---|
68 | |
---|
69 | // constraints: TimePicker.__Constraints |
---|
70 | // Specifies valid range of times (start time, end time) |
---|
71 | constraints: {}, |
---|
72 | |
---|
73 | /*===== |
---|
74 | serialize: function(val, options){ |
---|
75 | // summary: |
---|
76 | // User overridable function used to convert the attr('value') result to a String |
---|
77 | // val: Date |
---|
78 | // The current value |
---|
79 | // options: Object? |
---|
80 | // tags: |
---|
81 | // protected |
---|
82 | }, |
---|
83 | =====*/ |
---|
84 | serialize: stamp.toISOString, |
---|
85 | |
---|
86 | /*===== |
---|
87 | // filterString: string |
---|
88 | // The string to filter by |
---|
89 | filterString: "", |
---|
90 | =====*/ |
---|
91 | |
---|
92 | buildRendering: function(){ |
---|
93 | this.inherited(arguments); |
---|
94 | this.containerNode = this.domNode; // expected by _ListBase |
---|
95 | this.timeMenu = this.domNode; // for back-compat |
---|
96 | }, |
---|
97 | |
---|
98 | setValue: function(/*Date*/ value){ |
---|
99 | // summary: |
---|
100 | // Deprecated. Used set('value') instead. |
---|
101 | // tags: |
---|
102 | // deprecated |
---|
103 | kernel.deprecated("dijit._TimePicker:setValue() is deprecated. Use set('value', ...) instead.", "", "2.0"); |
---|
104 | this.set('value', value); |
---|
105 | }, |
---|
106 | |
---|
107 | _setValueAttr: function(/*Date*/ date){ |
---|
108 | // summary: |
---|
109 | // Hook so set('value', ...) works. |
---|
110 | // description: |
---|
111 | // Set the value of the TimePicker. |
---|
112 | // Redraws the TimePicker around the new date. |
---|
113 | // tags: |
---|
114 | // protected |
---|
115 | this._set("value", date); |
---|
116 | this._showText(); |
---|
117 | }, |
---|
118 | |
---|
119 | _setFilterStringAttr: function(val){ |
---|
120 | // summary: |
---|
121 | // Called by TimeTextBox to filter the values shown in my list |
---|
122 | this._set("filterString", val); |
---|
123 | this._showText(); |
---|
124 | }, |
---|
125 | |
---|
126 | isDisabledDate: function(/*===== dateObject, locale =====*/){ |
---|
127 | // summary: |
---|
128 | // May be overridden to disable certain dates in the TimePicker e.g. `isDisabledDate=locale.isWeekend` |
---|
129 | // dateObject: Date |
---|
130 | // locale: String? |
---|
131 | // type: |
---|
132 | // extension |
---|
133 | return false; // Boolean |
---|
134 | }, |
---|
135 | |
---|
136 | _getFilteredNodes: function(/*number*/ start, /*number*/ maxNum, /*Boolean*/ before, /*DOMNode*/ lastNode){ |
---|
137 | // summary: |
---|
138 | // Returns an array of nodes with the filter applied. At most maxNum nodes |
---|
139 | // will be returned - but fewer may be returned as well. If the |
---|
140 | // before parameter is set to true, then it will return the elements |
---|
141 | // before the given index |
---|
142 | // tags: |
---|
143 | // private |
---|
144 | |
---|
145 | var nodes = []; |
---|
146 | |
---|
147 | for(var i = 0 ; i < this._maxIncrement; i++){ |
---|
148 | var n = this._createOption(i); |
---|
149 | if(n){ |
---|
150 | nodes.push(n); |
---|
151 | } |
---|
152 | } |
---|
153 | return nodes; |
---|
154 | }, |
---|
155 | |
---|
156 | _showText: function(){ |
---|
157 | // summary: |
---|
158 | // Displays the relevant choices in the drop down list |
---|
159 | // tags: |
---|
160 | // private |
---|
161 | var fromIso = stamp.fromISOString; |
---|
162 | this.domNode.innerHTML = ""; |
---|
163 | this._clickableIncrementDate = fromIso(this.clickableIncrement); |
---|
164 | this._visibleIncrementDate = fromIso(this.visibleIncrement); |
---|
165 | // get the value of the increments to find out how many divs to create |
---|
166 | var |
---|
167 | sinceMidnight = function(/*Date*/ date){ |
---|
168 | return date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds(); |
---|
169 | }, |
---|
170 | clickableIncrementSeconds = sinceMidnight(this._clickableIncrementDate), |
---|
171 | visibleIncrementSeconds = sinceMidnight(this._visibleIncrementDate), |
---|
172 | // round reference date to previous visible increment |
---|
173 | time = (this.value || this.currentFocus).getTime(); |
---|
174 | |
---|
175 | this._refDate = fromIso("T00:00:00"); |
---|
176 | this._refDate.setFullYear(1970, 0, 1); // match parse defaults |
---|
177 | |
---|
178 | // assume clickable increment is the smallest unit |
---|
179 | this._clickableIncrement = 1; |
---|
180 | // divide the visible range by the clickable increment to get the number of divs to create |
---|
181 | // example: 10:00:00/00:15:00 -> display 40 divs |
---|
182 | // divide the visible increments by the clickable increments to get how often to display the time inline |
---|
183 | // example: 01:00:00/00:15:00 -> display the time every 4 divs |
---|
184 | this._visibleIncrement = visibleIncrementSeconds / clickableIncrementSeconds; |
---|
185 | // divide the number of seconds in a day by the clickable increment in seconds to get the |
---|
186 | // absolute max number of increments. |
---|
187 | this._maxIncrement = (60 * 60 * 24) / clickableIncrementSeconds; |
---|
188 | |
---|
189 | var nodes = this._getFilteredNodes(); |
---|
190 | array.forEach(nodes, function(n){ |
---|
191 | this.domNode.appendChild(n); |
---|
192 | }, this); |
---|
193 | |
---|
194 | // never show empty due to a bad filter |
---|
195 | if(!nodes.length && this.filterString){ |
---|
196 | this.filterString = ''; |
---|
197 | this._showText(); |
---|
198 | } |
---|
199 | }, |
---|
200 | |
---|
201 | constructor: function(/*===== params, srcNodeRef =====*/){ |
---|
202 | // summary: |
---|
203 | // Create the widget. |
---|
204 | // params: Object|null |
---|
205 | // Hash of initialization parameters for widget, including scalar values (like title, duration etc.) |
---|
206 | // and functions, typically callbacks like onClick. |
---|
207 | // The hash can contain any of the widget's properties, excluding read-only properties. |
---|
208 | // srcNodeRef: DOMNode|String? |
---|
209 | // If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree |
---|
210 | |
---|
211 | this.constraints = {}; |
---|
212 | }, |
---|
213 | |
---|
214 | postMixInProperties: function(){ |
---|
215 | this.inherited(arguments); |
---|
216 | this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls |
---|
217 | }, |
---|
218 | |
---|
219 | _setConstraintsAttr: function(/* Object */ constraints){ |
---|
220 | // brings in increments, etc. |
---|
221 | for (var key in { clickableIncrement: 1, visibleIncrement: 1 }) { |
---|
222 | if (key in constraints) { |
---|
223 | this[key] = constraints[key]; |
---|
224 | } |
---|
225 | } |
---|
226 | |
---|
227 | // locale needs the lang in the constraints as locale |
---|
228 | if(!constraints.locale){ |
---|
229 | constraints.locale = this.lang; |
---|
230 | } |
---|
231 | }, |
---|
232 | |
---|
233 | _createOption: function(/*Number*/ index){ |
---|
234 | // summary: |
---|
235 | // Creates a clickable time option, or returns null if the specified index doesn't match the filter |
---|
236 | // tags: |
---|
237 | // private |
---|
238 | var date = new Date(this._refDate); |
---|
239 | var incrementDate = this._clickableIncrementDate; |
---|
240 | date.setHours(date.getHours() + incrementDate.getHours() * index, |
---|
241 | date.getMinutes() + incrementDate.getMinutes() * index, |
---|
242 | date.getSeconds() + incrementDate.getSeconds() * index); |
---|
243 | if(this.constraints.selector == "time"){ |
---|
244 | date.setFullYear(1970, 0, 1); // make sure each time is for the same date |
---|
245 | } |
---|
246 | var dateString = locale.format(date, this.constraints); |
---|
247 | if(this.filterString && dateString.toLowerCase().indexOf(this.filterString) !== 0){ |
---|
248 | // Doesn't match the filter - return null |
---|
249 | return null; |
---|
250 | } |
---|
251 | |
---|
252 | var div = this.ownerDocument.createElement("div"); |
---|
253 | div.className = this.baseClass + "Item"; |
---|
254 | div.date = date; |
---|
255 | div.idx = index; |
---|
256 | domConstruct.create('div', { |
---|
257 | "class": this.baseClass + "ItemInner", |
---|
258 | innerHTML: dateString |
---|
259 | }, div); |
---|
260 | |
---|
261 | if(index % this._visibleIncrement < 1 && index % this._visibleIncrement > -1){ |
---|
262 | domClass.add(div, this.baseClass + "Marker"); |
---|
263 | }else if(!(index % this._clickableIncrement)){ |
---|
264 | domClass.add(div, this.baseClass + "Tick"); |
---|
265 | } |
---|
266 | |
---|
267 | if(this.isDisabledDate(date)){ |
---|
268 | // set disabled |
---|
269 | domClass.add(div, this.baseClass + "ItemDisabled"); |
---|
270 | } |
---|
271 | if(this.value && !ddate.compare(this.value, date, this.constraints.selector)){ |
---|
272 | div.selected = true; |
---|
273 | domClass.add(div, this.baseClass + "ItemSelected"); |
---|
274 | this._selectedDiv = div; |
---|
275 | if(domClass.contains(div, this.baseClass + "Marker")){ |
---|
276 | domClass.add(div, this.baseClass + "MarkerSelected"); |
---|
277 | }else{ |
---|
278 | domClass.add(div, this.baseClass + "TickSelected"); |
---|
279 | } |
---|
280 | |
---|
281 | // Initially highlight the current value. User can change highlight by up/down arrow keys |
---|
282 | // or mouse movement. |
---|
283 | this._highlightOption(div, true); |
---|
284 | } |
---|
285 | return div; |
---|
286 | }, |
---|
287 | |
---|
288 | onOpen: function(){ |
---|
289 | this.inherited(arguments); |
---|
290 | |
---|
291 | // Since _ListBase::_setSelectedAttr() calls scrollIntoView(), shouldn't call it until list is visible. |
---|
292 | this.set("selected", this._selectedDiv); |
---|
293 | }, |
---|
294 | |
---|
295 | _onOptionSelected: function(/*Object*/ tgt){ |
---|
296 | // summary: |
---|
297 | // Called when user clicks an option in the drop down list |
---|
298 | // tags: |
---|
299 | // private |
---|
300 | var tdate = tgt.target.date || tgt.target.parentNode.date; |
---|
301 | if(!tdate || this.isDisabledDate(tdate)){ |
---|
302 | return; |
---|
303 | } |
---|
304 | this._highlighted_option = null; |
---|
305 | this.set('value', tdate); |
---|
306 | this.onChange(tdate); |
---|
307 | }, |
---|
308 | |
---|
309 | onChange: function(/*Date*/ /*===== time =====*/){ |
---|
310 | // summary: |
---|
311 | // Notification that a time was selected. It may be the same as the previous value. |
---|
312 | // tags: |
---|
313 | // public |
---|
314 | }, |
---|
315 | |
---|
316 | _highlightOption: function(/*node*/ node, /*Boolean*/ highlight){ |
---|
317 | // summary: |
---|
318 | // Turns on/off highlight effect on a node based on mouse out/over event |
---|
319 | // tags: |
---|
320 | // private |
---|
321 | if(!node){ |
---|
322 | return; |
---|
323 | } |
---|
324 | if(highlight){ |
---|
325 | if(this._highlighted_option){ |
---|
326 | this._highlightOption(this._highlighted_option, false); |
---|
327 | } |
---|
328 | this._highlighted_option = node; |
---|
329 | }else if(this._highlighted_option !== node){ |
---|
330 | return; |
---|
331 | }else{ |
---|
332 | this._highlighted_option = null; |
---|
333 | } |
---|
334 | domClass.toggle(node, this.baseClass + "ItemHover", highlight); |
---|
335 | if(domClass.contains(node, this.baseClass + "Marker")){ |
---|
336 | domClass.toggle(node, this.baseClass + "MarkerHover", highlight); |
---|
337 | }else{ |
---|
338 | domClass.toggle(node, this.baseClass + "TickHover", highlight); |
---|
339 | } |
---|
340 | }, |
---|
341 | |
---|
342 | handleKey: function(/*Event*/ e){ |
---|
343 | // summary: |
---|
344 | // Called from `dijit/form/_DateTimeTextBox` to pass a keypress event |
---|
345 | // from the `dijit/form/TimeTextBox` to be handled in this widget |
---|
346 | // tags: |
---|
347 | // protected |
---|
348 | if(e.keyCode == keys.DOWN_ARROW){ |
---|
349 | this.selectNextNode(); |
---|
350 | e.stopPropagation(); |
---|
351 | e.preventDefault(); |
---|
352 | return false; |
---|
353 | }else if(e.keyCode == keys.UP_ARROW){ |
---|
354 | this.selectPreviousNode(); |
---|
355 | e.stopPropagation(); |
---|
356 | e.preventDefault(); |
---|
357 | return false; |
---|
358 | }else if(e.keyCode == keys.ENTER || e.keyCode === keys.TAB){ |
---|
359 | // mouse hover followed by TAB is NO selection |
---|
360 | if(!this._keyboardSelected && e.keyCode === keys.TAB){ |
---|
361 | return true; // true means don't call stopEvent() |
---|
362 | } |
---|
363 | |
---|
364 | // Accept the currently-highlighted option as the value |
---|
365 | if(this._highlighted_option){ |
---|
366 | this._onOptionSelected({target: this._highlighted_option}); |
---|
367 | } |
---|
368 | |
---|
369 | // Call stopEvent() for ENTER key so that form doesn't submit, |
---|
370 | // but not for TAB, so that TAB does switch focus |
---|
371 | return e.keyCode === keys.TAB; |
---|
372 | } |
---|
373 | return undefined; |
---|
374 | }, |
---|
375 | |
---|
376 | // Implement abstract methods for _ListBase |
---|
377 | onHover: function(/*DomNode*/ node){ |
---|
378 | this._highlightOption(node, true); |
---|
379 | }, |
---|
380 | |
---|
381 | onUnhover: function(/*DomNode*/ node){ |
---|
382 | this._highlightOption(node, false); |
---|
383 | }, |
---|
384 | |
---|
385 | onSelect: function(/*DomNode*/ node){ |
---|
386 | this._highlightOption(node, true); |
---|
387 | }, |
---|
388 | |
---|
389 | onDeselect: function(/*DomNode*/ node){ |
---|
390 | this._highlightOption(node, false); |
---|
391 | }, |
---|
392 | |
---|
393 | onClick: function(/*DomNode*/ node){ |
---|
394 | this._onOptionSelected({target: node}); |
---|
395 | } |
---|
396 | }); |
---|
397 | |
---|
398 | /*===== |
---|
399 | TimePicker.__Constraints = declare(locale.__FormatOptions, { |
---|
400 | // clickableIncrement: String |
---|
401 | // See `dijit/_TimePicker.clickableIncrement` |
---|
402 | clickableIncrement: "T00:15:00" |
---|
403 | }); |
---|
404 | =====*/ |
---|
405 | |
---|
406 | return TimePicker; |
---|
407 | }); |
---|