[483] | 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 | }); |
---|