_TimePicker.js 18 KB

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