_TimePicker.js 17 KB

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