jquery.timepicker.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386
  1. /*!
  2. * jquery-timepicker v1.11.14 - A jQuery timepicker plugin inspired by Google Calendar. It supports both mouse and keyboard navigation.
  3. * Copyright (c) 2015 Jon Thornton - http://jonthornton.github.com/jquery-timepicker/
  4. * License: MIT
  5. */
  6. (function(factory) {
  7. if (
  8. typeof exports === "object" &&
  9. exports &&
  10. typeof module === "object" &&
  11. module &&
  12. module.exports === exports
  13. ) {
  14. // Browserify. Attach to jQuery module.
  15. factory(require("jquery"));
  16. } else if (typeof define === "function" && define.amd) {
  17. // AMD. Register as an anonymous module.
  18. define(["jquery"], factory);
  19. } else {
  20. // Browser globals
  21. factory(jQuery);
  22. }
  23. })(function($) {
  24. var _ONE_DAY = 86400;
  25. var _lang = {
  26. am: "am",
  27. pm: "pm",
  28. AM: "AM",
  29. PM: "PM",
  30. decimal: ".",
  31. mins: "mins",
  32. hr: "hr",
  33. hrs: "hrs"
  34. };
  35. var _DEFAULTS = {
  36. appendTo: "body",
  37. className: null,
  38. closeOnWindowScroll: false,
  39. disableTextInput: false,
  40. disableTimeRanges: [],
  41. disableTouchKeyboard: false,
  42. durationTime: null,
  43. forceRoundTime: false,
  44. maxTime: null,
  45. minTime: null,
  46. noneOption: false,
  47. orientation: "l",
  48. roundingFunction: function(seconds, settings) {
  49. if (seconds === null) {
  50. return null;
  51. } else if (typeof settings.step !== "number") {
  52. // TODO: nearest fit irregular steps
  53. return seconds;
  54. } else {
  55. var offset = seconds % (settings.step * 60); // step is in minutes
  56. var start = settings.minTime || 0;
  57. // adjust offset by start mod step so that the offset is aligned not to 00:00 but to the start
  58. offset -= start % (settings.step * 60);
  59. if (offset >= settings.step * 30) {
  60. // if offset is larger than a half step, round up
  61. seconds += settings.step * 60 - offset;
  62. } else {
  63. // round down
  64. seconds -= offset;
  65. }
  66. return _moduloSeconds(seconds, settings);
  67. }
  68. },
  69. scrollDefault: null,
  70. selectOnBlur: false,
  71. show2400: false,
  72. showDuration: false,
  73. showOn: ["click", "focus"],
  74. showOnFocus: true,
  75. step: 30,
  76. stopScrollPropagation: false,
  77. timeFormat: "g:ia",
  78. typeaheadHighlight: true,
  79. useSelect: false,
  80. wrapHours: true
  81. };
  82. var methods = {
  83. init: function(options) {
  84. return this.each(function() {
  85. var self = $(this);
  86. // pick up settings from data attributes
  87. var attributeOptions = [];
  88. for (var key in _DEFAULTS) {
  89. if (self.data(key)) {
  90. attributeOptions[key] = self.data(key);
  91. }
  92. }
  93. var settings = $.extend({}, _DEFAULTS, options, attributeOptions);
  94. if (settings.lang) {
  95. _lang = $.extend(_lang, settings.lang);
  96. }
  97. settings = _parseSettings(settings);
  98. self.data("timepicker-settings", settings);
  99. self.addClass("ui-timepicker-input");
  100. if (settings.useSelect) {
  101. _render(self);
  102. } else {
  103. self.prop("autocomplete", "off");
  104. if (settings.showOn) {
  105. for (var i in settings.showOn) {
  106. self.on(settings.showOn[i] + ".timepicker", methods.show);
  107. }
  108. }
  109. self.on("change.timepicker", _formatValue);
  110. self.on("keydown.timepicker", _keydownhandler);
  111. self.on("keyup.timepicker", _keyuphandler);
  112. if (settings.disableTextInput) {
  113. self.on("keydown.timepicker", _disableTextInputHandler);
  114. }
  115. self.on("cut.timepicker", _keyuphandler);
  116. self.on("paste.timepicker", _keyuphandler);
  117. _formatValue.call(self.get(0), null, "initial");
  118. }
  119. });
  120. },
  121. show: function(e) {
  122. var self = $(this);
  123. var settings = self.data("timepicker-settings");
  124. if (e) {
  125. e.preventDefault();
  126. }
  127. if (settings.useSelect) {
  128. self.data("timepicker-list").focus();
  129. return;
  130. }
  131. if (_hideKeyboard(self)) {
  132. // block the keyboard on mobile devices
  133. self.blur();
  134. }
  135. var list = self.data("timepicker-list");
  136. // check if input is readonly
  137. if (self.prop("readonly")) {
  138. return;
  139. }
  140. // check if list needs to be rendered
  141. if (
  142. !list ||
  143. list.length === 0 ||
  144. typeof settings.durationTime === "function"
  145. ) {
  146. _render(self);
  147. list = self.data("timepicker-list");
  148. }
  149. if (_isVisible(list)) {
  150. return;
  151. }
  152. self.data("ui-timepicker-value", self.val());
  153. _setSelected(self, list);
  154. // make sure other pickers are hidden
  155. methods.hide();
  156. // position the dropdown relative to the input
  157. list.show();
  158. var listOffset = {};
  159. if (settings.orientation.match(/r/)) {
  160. // right-align the dropdown
  161. listOffset.left =
  162. self.offset().left +
  163. self.outerWidth() -
  164. list.outerWidth() +
  165. parseInt(list.css("marginLeft").replace("px", ""), 10);
  166. } else {
  167. // left-align the dropdown
  168. listOffset.left =
  169. self.offset().left +
  170. parseInt(list.css("marginLeft").replace("px", ""), 10);
  171. }
  172. var verticalOrientation;
  173. if (settings.orientation.match(/t/)) {
  174. verticalOrientation = "t";
  175. } else if (settings.orientation.match(/b/)) {
  176. verticalOrientation = "b";
  177. } else if (
  178. self.offset().top + self.outerHeight(true) + list.outerHeight() >
  179. $(window).height() + $(window).scrollTop()
  180. ) {
  181. verticalOrientation = "t";
  182. } else {
  183. verticalOrientation = "b";
  184. }
  185. if (verticalOrientation == "t") {
  186. // position the dropdown on top
  187. list.addClass("ui-timepicker-positioned-top");
  188. listOffset.top =
  189. self.offset().top -
  190. list.outerHeight() +
  191. parseInt(list.css("marginTop").replace("px", ""), 10);
  192. } else {
  193. // put it under the input
  194. list.removeClass("ui-timepicker-positioned-top");
  195. listOffset.top =
  196. self.offset().top +
  197. self.outerHeight() +
  198. parseInt(list.css("marginTop").replace("px", ""), 10);
  199. }
  200. list.offset(listOffset);
  201. // position scrolling
  202. var selected = list.find(".ui-timepicker-selected");
  203. if (!selected.length) {
  204. var timeInt = _time2int(_getTimeValue(self));
  205. if (timeInt !== null) {
  206. selected = _findRow(self, list, timeInt);
  207. } else if (settings.scrollDefault) {
  208. selected = _findRow(self, list, settings.scrollDefault());
  209. }
  210. }
  211. // if not found or disabled, intelligently find first selectable element
  212. if (!selected.length || selected.hasClass("ui-timepicker-disabled")) {
  213. selected = list.find("li:not(.ui-timepicker-disabled):first");
  214. }
  215. if (selected && selected.length) {
  216. var topOffset =
  217. list.scrollTop() + selected.position().top - selected.outerHeight();
  218. list.scrollTop(topOffset);
  219. } else {
  220. list.scrollTop(0);
  221. }
  222. // prevent scroll propagation
  223. if (settings.stopScrollPropagation) {
  224. $(
  225. document
  226. ).on("wheel.ui-timepicker", ".ui-timepicker-wrapper", function(e) {
  227. e.preventDefault();
  228. var currentScroll = $(this).scrollTop();
  229. $(this).scrollTop(currentScroll + e.originalEvent.deltaY);
  230. });
  231. }
  232. // attach close handlers
  233. $(document).on(
  234. "touchstart.ui-timepicker mousedown.ui-timepicker",
  235. _closeHandler
  236. );
  237. $(window).on("resize.ui-timepicker", _closeHandler);
  238. if (settings.closeOnWindowScroll) {
  239. $(document).on("scroll.ui-timepicker", _closeHandler);
  240. }
  241. self.trigger("showTimepicker");
  242. return this;
  243. },
  244. hide: function(e) {
  245. var self = $(this);
  246. var settings = self.data("timepicker-settings");
  247. if (settings && settings.useSelect) {
  248. self.blur();
  249. }
  250. $(".ui-timepicker-wrapper").each(function() {
  251. var list = $(this);
  252. if (!_isVisible(list)) {
  253. return;
  254. }
  255. var self = list.data("timepicker-input");
  256. var settings = self.data("timepicker-settings");
  257. if (settings && settings.selectOnBlur) {
  258. _selectValue(self);
  259. }
  260. list.hide();
  261. self.trigger("hideTimepicker");
  262. });
  263. return this;
  264. },
  265. option: function(key, value) {
  266. if (typeof key == "string" && typeof value == "undefined") {
  267. return $(this).data("timepicker-settings")[key];
  268. }
  269. return this.each(function() {
  270. var self = $(this);
  271. var settings = self.data("timepicker-settings");
  272. var list = self.data("timepicker-list");
  273. if (typeof key == "object") {
  274. settings = $.extend(settings, key);
  275. } else if (typeof key == "string") {
  276. settings[key] = value;
  277. }
  278. settings = _parseSettings(settings);
  279. self.data("timepicker-settings", settings);
  280. _formatValue.call(self.get(0), { type: "change" }, "initial");
  281. if (list) {
  282. list.remove();
  283. self.data("timepicker-list", false);
  284. }
  285. if (settings.useSelect) {
  286. _render(self);
  287. }
  288. });
  289. },
  290. getSecondsFromMidnight: function() {
  291. return _time2int(_getTimeValue(this));
  292. },
  293. getTime: function(relative_date) {
  294. var self = this;
  295. var time_string = _getTimeValue(self);
  296. if (!time_string) {
  297. return null;
  298. }
  299. var offset = _time2int(time_string);
  300. if (offset === null) {
  301. return null;
  302. }
  303. if (!relative_date) {
  304. relative_date = new Date();
  305. }
  306. // construct a Date from relative date, and offset's time
  307. var time = new Date(relative_date);
  308. time.setHours(offset / 3600);
  309. time.setMinutes((offset % 3600) / 60);
  310. time.setSeconds(offset % 60);
  311. time.setMilliseconds(0);
  312. return time;
  313. },
  314. isVisible: function() {
  315. var self = this;
  316. var list = self.data("timepicker-list");
  317. return !!(list && _isVisible(list));
  318. },
  319. setTime: function(value) {
  320. var self = this;
  321. var settings = self.data("timepicker-settings");
  322. if (settings.forceRoundTime) {
  323. var prettyTime = _roundAndFormatTime(_time2int(value), settings);
  324. } else {
  325. var prettyTime = _int2time(_time2int(value), settings);
  326. }
  327. if (value && prettyTime === null && settings.noneOption) {
  328. prettyTime = value;
  329. }
  330. _setTimeValue(self, prettyTime, "initial");
  331. _formatValue.call(self.get(0), { type: "change" }, "initial");
  332. if (self.data("timepicker-list")) {
  333. _setSelected(self, self.data("timepicker-list"));
  334. }
  335. return this;
  336. },
  337. remove: function() {
  338. var self = this;
  339. // check if this element is a timepicker
  340. if (!self.hasClass("ui-timepicker-input")) {
  341. return;
  342. }
  343. var settings = self.data("timepicker-settings");
  344. self.removeAttr("autocomplete", "off");
  345. self.removeClass("ui-timepicker-input");
  346. self.removeData("timepicker-settings");
  347. self.off(".timepicker");
  348. // timepicker-list won't be present unless the user has interacted with this timepicker
  349. if (self.data("timepicker-list")) {
  350. self.data("timepicker-list").remove();
  351. }
  352. if (settings.useSelect) {
  353. self.show();
  354. }
  355. self.removeData("timepicker-list");
  356. return this;
  357. }
  358. };
  359. // private methods
  360. function _isVisible(elem) {
  361. var el = elem[0];
  362. return el.offsetWidth > 0 && el.offsetHeight > 0;
  363. }
  364. function _parseSettings(settings) {
  365. if (settings.minTime) {
  366. settings.minTime = _time2int(settings.minTime);
  367. }
  368. if (settings.maxTime) {
  369. settings.maxTime = _time2int(settings.maxTime);
  370. }
  371. if (settings.durationTime && typeof settings.durationTime !== "function") {
  372. settings.durationTime = _time2int(settings.durationTime);
  373. }
  374. if (settings.scrollDefault == "now") {
  375. settings.scrollDefault = function() {
  376. return settings.roundingFunction(_time2int(new Date()), settings);
  377. };
  378. } else if (
  379. settings.scrollDefault &&
  380. typeof settings.scrollDefault != "function"
  381. ) {
  382. var val = settings.scrollDefault;
  383. settings.scrollDefault = function() {
  384. return settings.roundingFunction(_time2int(val), settings);
  385. };
  386. } else if (settings.minTime) {
  387. settings.scrollDefault = function() {
  388. return settings.roundingFunction(settings.minTime, settings);
  389. };
  390. }
  391. if (
  392. $.type(settings.timeFormat) === "string" &&
  393. settings.timeFormat.match(/[gh]/)
  394. ) {
  395. settings._twelveHourTime = true;
  396. }
  397. if (
  398. settings.showOnFocus === false &&
  399. settings.showOn.indexOf("focus") != -1
  400. ) {
  401. settings.showOn.splice(settings.showOn.indexOf("focus"), 1);
  402. }
  403. if (settings.disableTimeRanges.length > 0) {
  404. // convert string times to integers
  405. for (var i in settings.disableTimeRanges) {
  406. settings.disableTimeRanges[i] = [
  407. _time2int(settings.disableTimeRanges[i][0]),
  408. _time2int(settings.disableTimeRanges[i][1])
  409. ];
  410. }
  411. // sort by starting time
  412. settings.disableTimeRanges = settings.disableTimeRanges.sort(function(
  413. a,
  414. b
  415. ) {
  416. return a[0] - b[0];
  417. });
  418. // merge any overlapping ranges
  419. for (var i = settings.disableTimeRanges.length - 1; i > 0; i--) {
  420. if (
  421. settings.disableTimeRanges[i][0] <=
  422. settings.disableTimeRanges[i - 1][1]
  423. ) {
  424. settings.disableTimeRanges[i - 1] = [
  425. Math.min(
  426. settings.disableTimeRanges[i][0],
  427. settings.disableTimeRanges[i - 1][0]
  428. ),
  429. Math.max(
  430. settings.disableTimeRanges[i][1],
  431. settings.disableTimeRanges[i - 1][1]
  432. )
  433. ];
  434. settings.disableTimeRanges.splice(i, 1);
  435. }
  436. }
  437. }
  438. return settings;
  439. }
  440. function _render(self) {
  441. var settings = self.data("timepicker-settings");
  442. var list = self.data("timepicker-list");
  443. if (list && list.length) {
  444. list.remove();
  445. self.data("timepicker-list", false);
  446. }
  447. if (settings.useSelect) {
  448. list = $("<select />", { class: "ui-timepicker-select" });
  449. if (self.attr('name')) {
  450. list.attr('name', 'ui-timepicker-' + self.attr('name'));
  451. }
  452. var wrapped_list = list;
  453. } else {
  454. list = $("<ul />", { class: "ui-timepicker-list" });
  455. var wrapped_list = $("<div />", {
  456. class: "ui-timepicker-wrapper",
  457. tabindex: -1
  458. });
  459. wrapped_list.css({ display: "none", position: "absolute" }).append(list);
  460. }
  461. if (settings.noneOption) {
  462. if (settings.noneOption === true) {
  463. settings.noneOption = settings.useSelect ? "Time..." : "None";
  464. }
  465. if ($.isArray(settings.noneOption)) {
  466. for (var i in settings.noneOption) {
  467. if (parseInt(i, 10) == i) {
  468. var noneElement = _generateNoneElement(
  469. settings.noneOption[i],
  470. settings.useSelect
  471. );
  472. list.append(noneElement);
  473. }
  474. }
  475. } else {
  476. var noneElement = _generateNoneElement(
  477. settings.noneOption,
  478. settings.useSelect
  479. );
  480. list.append(noneElement);
  481. }
  482. }
  483. if (settings.className) {
  484. wrapped_list.addClass(settings.className);
  485. }
  486. if (
  487. (settings.minTime !== null || settings.durationTime !== null) &&
  488. settings.showDuration
  489. ) {
  490. var stepval =
  491. typeof settings.step == "function" ? "function" : settings.step;
  492. wrapped_list.addClass("ui-timepicker-with-duration");
  493. wrapped_list.addClass("ui-timepicker-step-" + settings.step);
  494. }
  495. var durStart = settings.minTime;
  496. if (typeof settings.durationTime === "function") {
  497. durStart = _time2int(settings.durationTime());
  498. } else if (settings.durationTime !== null) {
  499. durStart = settings.durationTime;
  500. }
  501. var start = settings.minTime !== null ? settings.minTime : 0;
  502. var end =
  503. settings.maxTime !== null ? settings.maxTime : start + _ONE_DAY - 1;
  504. if (end < start) {
  505. // make sure the end time is greater than start time, otherwise there will be no list to show
  506. end += _ONE_DAY;
  507. }
  508. if (
  509. end === _ONE_DAY - 1 &&
  510. $.type(settings.timeFormat) === "string" &&
  511. settings.show2400
  512. ) {
  513. // show a 24:00 option when using military time
  514. end = _ONE_DAY;
  515. }
  516. var dr = settings.disableTimeRanges;
  517. var drCur = 0;
  518. var drLen = dr.length;
  519. var stepFunc = settings.step;
  520. if (typeof stepFunc != "function") {
  521. stepFunc = function() {
  522. return settings.step;
  523. };
  524. }
  525. for (var i = start, j = 0; i <= end; j++, i += stepFunc(j) * 60) {
  526. var timeInt = i;
  527. var timeString = _int2time(timeInt, settings);
  528. if (settings.useSelect) {
  529. var row = $("<option />", { value: timeString });
  530. row.text(timeString);
  531. } else {
  532. var row = $("<li />");
  533. row.addClass(
  534. timeInt % _ONE_DAY < _ONE_DAY / 2
  535. ? "ui-timepicker-am"
  536. : "ui-timepicker-pm"
  537. );
  538. row.data("time", _moduloSeconds(timeInt, settings));
  539. row.text(timeString);
  540. }
  541. if (
  542. (settings.minTime !== null || settings.durationTime !== null) &&
  543. settings.showDuration
  544. ) {
  545. var durationString = _int2duration(i - durStart, settings.step);
  546. if (settings.useSelect) {
  547. row.text(row.text() + " (" + durationString + ")");
  548. } else {
  549. var duration = $("<span />", { class: "ui-timepicker-duration" });
  550. duration.text(" (" + durationString + ")");
  551. row.append(duration);
  552. }
  553. }
  554. if (drCur < drLen) {
  555. if (timeInt >= dr[drCur][1]) {
  556. drCur += 1;
  557. }
  558. if (dr[drCur] && timeInt >= dr[drCur][0] && timeInt < dr[drCur][1]) {
  559. if (settings.useSelect) {
  560. row.prop("disabled", true);
  561. } else {
  562. row.addClass("ui-timepicker-disabled");
  563. }
  564. }
  565. }
  566. list.append(row);
  567. }
  568. wrapped_list.data("timepicker-input", self);
  569. self.data("timepicker-list", wrapped_list);
  570. if (settings.useSelect) {
  571. if (self.val()) {
  572. list.val(_roundAndFormatTime(_time2int(self.val()), settings));
  573. }
  574. list.on("focus", function() {
  575. $(this)
  576. .data("timepicker-input")
  577. .trigger("showTimepicker");
  578. });
  579. list.on("blur", function() {
  580. $(this)
  581. .data("timepicker-input")
  582. .trigger("hideTimepicker");
  583. });
  584. list.on("change", function() {
  585. _setTimeValue(self, $(this).val(), "select");
  586. });
  587. _setTimeValue(self, list.val(), "initial");
  588. self.hide().after(list);
  589. } else {
  590. var appendTo = settings.appendTo;
  591. if (typeof appendTo === "string") {
  592. appendTo = $(appendTo);
  593. } else if (typeof appendTo === "function") {
  594. appendTo = appendTo(self);
  595. }
  596. appendTo.append(wrapped_list);
  597. _setSelected(self, list);
  598. list.on("mousedown click", "li", function(e) {
  599. // hack: temporarily disable the focus handler
  600. // to deal with the fact that IE fires 'focus'
  601. // events asynchronously
  602. self.off("focus.timepicker");
  603. self.on("focus.timepicker-ie-hack", function() {
  604. self.off("focus.timepicker-ie-hack");
  605. self.on("focus.timepicker", methods.show);
  606. });
  607. if (!_hideKeyboard(self)) {
  608. self[0].focus();
  609. }
  610. // make sure only the clicked row is selected
  611. list.find("li").removeClass("ui-timepicker-selected");
  612. $(this).addClass("ui-timepicker-selected");
  613. if (_selectValue(self)) {
  614. self.trigger("hideTimepicker");
  615. list.on("mouseup.timepicker click.timepicker", "li", function(e) {
  616. list.off("mouseup.timepicker click.timepicker");
  617. wrapped_list.hide();
  618. });
  619. }
  620. });
  621. }
  622. }
  623. function _generateNoneElement(optionValue, useSelect) {
  624. var label, className, value;
  625. if (typeof optionValue == "object") {
  626. label = optionValue.label;
  627. className = optionValue.className;
  628. value = optionValue.value;
  629. } else if (typeof optionValue == "string") {
  630. label = optionValue;
  631. value = '';
  632. } else {
  633. $.error("Invalid noneOption value");
  634. }
  635. if (useSelect) {
  636. return $("<option />", {
  637. value: value,
  638. class: className,
  639. text: label
  640. });
  641. } else {
  642. return $("<li />", {
  643. class: className,
  644. text: label
  645. }).data("time", String(value));
  646. }
  647. }
  648. function _roundAndFormatTime(seconds, settings) {
  649. seconds = settings.roundingFunction(seconds, settings);
  650. if (seconds !== null) {
  651. return _int2time(seconds, settings);
  652. }
  653. }
  654. // event handler to decide whether to close timepicker
  655. function _closeHandler(e) {
  656. if (e.target == window) {
  657. // mobile Chrome fires focus events against window for some reason
  658. return;
  659. }
  660. var target = $(e.target);
  661. if (
  662. target.closest(".ui-timepicker-input").length ||
  663. target.closest(".ui-timepicker-wrapper").length
  664. ) {
  665. // active timepicker was focused. ignore
  666. return;
  667. }
  668. methods.hide();
  669. $(document).unbind(".ui-timepicker");
  670. $(window).unbind(".ui-timepicker");
  671. }
  672. function _hideKeyboard(self) {
  673. var settings = self.data("timepicker-settings");
  674. return (
  675. (window.navigator.msMaxTouchPoints || "ontouchstart" in document) &&
  676. settings.disableTouchKeyboard
  677. );
  678. }
  679. function _findRow(self, list, value) {
  680. if (!value && value !== 0) {
  681. return false;
  682. }
  683. var settings = self.data("timepicker-settings");
  684. var out = false;
  685. var value = settings.roundingFunction(value, settings);
  686. // loop through the menu items
  687. list.find("li").each(function(i, obj) {
  688. var jObj = $(obj);
  689. if (typeof jObj.data("time") != "number") {
  690. return;
  691. }
  692. if (jObj.data("time") == value) {
  693. out = jObj;
  694. return false;
  695. }
  696. });
  697. return out;
  698. }
  699. function _setSelected(self, list) {
  700. list.find("li").removeClass("ui-timepicker-selected");
  701. var settings = self.data("timepicker-settings");
  702. var timeValue = _time2int(_getTimeValue(self), settings);
  703. if (timeValue === null) {
  704. return;
  705. }
  706. var selected = _findRow(self, list, timeValue);
  707. if (selected) {
  708. var topDelta = selected.offset().top - list.offset().top;
  709. if (
  710. topDelta + selected.outerHeight() > list.outerHeight() ||
  711. topDelta < 0
  712. ) {
  713. list.scrollTop(
  714. list.scrollTop() + selected.position().top - selected.outerHeight()
  715. );
  716. }
  717. if (settings.forceRoundTime || selected.data("time") === timeValue) {
  718. selected.addClass("ui-timepicker-selected");
  719. }
  720. }
  721. }
  722. function _formatValue(e, origin) {
  723. if (origin == "timepicker") {
  724. return;
  725. }
  726. var self = $(this);
  727. if (this.value === "") {
  728. _setTimeValue(self, null, origin);
  729. return;
  730. }
  731. if (self.is(":focus") && (!e || e.type != "change")) {
  732. return;
  733. }
  734. var settings = self.data("timepicker-settings");
  735. var seconds = _time2int(this.value, settings);
  736. if (seconds === null) {
  737. self.trigger("timeFormatError");
  738. return;
  739. }
  740. var rangeError = false;
  741. // check that the time in within bounds
  742. if (
  743. settings.minTime !== null &&
  744. settings.maxTime !== null &&
  745. (seconds < settings.minTime || seconds > settings.maxTime)
  746. ) {
  747. rangeError = true;
  748. }
  749. // check that time isn't within disabled time ranges
  750. $.each(settings.disableTimeRanges, function() {
  751. if (seconds >= this[0] && seconds < this[1]) {
  752. rangeError = true;
  753. return false;
  754. }
  755. });
  756. if (settings.forceRoundTime) {
  757. var roundSeconds = settings.roundingFunction(seconds, settings);
  758. if (roundSeconds != seconds) {
  759. seconds = roundSeconds;
  760. origin = null;
  761. }
  762. }
  763. var prettyTime = _int2time(seconds, settings);
  764. if (rangeError) {
  765. if (
  766. _setTimeValue(self, prettyTime, "error") ||
  767. (e && e.type == "change")
  768. ) {
  769. self.trigger("timeRangeError");
  770. }
  771. } else {
  772. _setTimeValue(self, prettyTime, origin);
  773. }
  774. }
  775. function _getTimeValue(self) {
  776. if (self.is("input")) {
  777. return self.val();
  778. } else {
  779. // use the element's data attributes to store values
  780. return self.data("ui-timepicker-value");
  781. }
  782. }
  783. function _setTimeValue(self, value, source) {
  784. if (self.is("input")) {
  785. self.val(value);
  786. var settings = self.data("timepicker-settings");
  787. if (settings.useSelect && source != "select" && self.data("timepicker-list")) {
  788. self
  789. .data("timepicker-list")
  790. .val(_roundAndFormatTime(_time2int(value), settings));
  791. }
  792. }
  793. if (self.data("ui-timepicker-value") != value) {
  794. self.data("ui-timepicker-value", value);
  795. if (source == "select") {
  796. self
  797. .trigger("selectTime")
  798. .trigger("changeTime")
  799. .trigger("change", "timepicker");
  800. } else if (["error", "initial"].indexOf(source) == -1) {
  801. self.trigger("changeTime");
  802. }
  803. return true;
  804. } else {
  805. if (["error", "initial"].indexOf(source) == -1) {
  806. self.trigger("selectTime");
  807. }
  808. return false;
  809. }
  810. }
  811. /*
  812. * Filter freeform input
  813. */
  814. function _disableTextInputHandler(e) {
  815. switch (e.keyCode) {
  816. case 13: // return
  817. case 9: //tab
  818. return;
  819. default:
  820. e.preventDefault();
  821. }
  822. }
  823. /*
  824. * Keyboard navigation via arrow keys
  825. */
  826. function _keydownhandler(e) {
  827. var self = $(this);
  828. var list = self.data("timepicker-list");
  829. if (!list || !_isVisible(list)) {
  830. if (e.keyCode == 40) {
  831. // show the list!
  832. methods.show.call(self.get(0));
  833. list = self.data("timepicker-list");
  834. if (!_hideKeyboard(self)) {
  835. self.focus();
  836. }
  837. } else {
  838. return true;
  839. }
  840. }
  841. switch (e.keyCode) {
  842. case 13: // return
  843. if (_selectValue(self)) {
  844. _formatValue.call(self.get(0), { type: "change" });
  845. methods.hide.apply(this);
  846. }
  847. e.preventDefault();
  848. return false;
  849. case 38: // up
  850. var selected = list.find(".ui-timepicker-selected");
  851. if (!selected.length) {
  852. list.find("li").each(function(i, obj) {
  853. if ($(obj).position().top > 0) {
  854. selected = $(obj);
  855. return false;
  856. }
  857. });
  858. selected.addClass("ui-timepicker-selected");
  859. } else if (!selected.is(":first-child")) {
  860. selected.removeClass("ui-timepicker-selected");
  861. selected.prev().addClass("ui-timepicker-selected");
  862. if (selected.prev().position().top < selected.outerHeight()) {
  863. list.scrollTop(list.scrollTop() - selected.outerHeight());
  864. }
  865. }
  866. return false;
  867. case 40: // down
  868. selected = list.find(".ui-timepicker-selected");
  869. if (selected.length === 0) {
  870. list.find("li").each(function(i, obj) {
  871. if ($(obj).position().top > 0) {
  872. selected = $(obj);
  873. return false;
  874. }
  875. });
  876. selected.addClass("ui-timepicker-selected");
  877. } else if (!selected.is(":last-child")) {
  878. selected.removeClass("ui-timepicker-selected");
  879. selected.next().addClass("ui-timepicker-selected");
  880. if (
  881. selected.next().position().top + 2 * selected.outerHeight() >
  882. list.outerHeight()
  883. ) {
  884. list.scrollTop(list.scrollTop() + selected.outerHeight());
  885. }
  886. }
  887. return false;
  888. case 27: // escape
  889. list.find("li").removeClass("ui-timepicker-selected");
  890. methods.hide();
  891. break;
  892. case 9: //tab
  893. methods.hide();
  894. break;
  895. default:
  896. return true;
  897. }
  898. }
  899. /*
  900. * Time typeahead
  901. */
  902. function _keyuphandler(e) {
  903. var self = $(this);
  904. var list = self.data("timepicker-list");
  905. var settings = self.data("timepicker-settings");
  906. if (!list || !_isVisible(list) || settings.disableTextInput) {
  907. return true;
  908. }
  909. if (e.type === "paste" || e.type === "cut") {
  910. setTimeout(function() {
  911. if (settings.typeaheadHighlight) {
  912. _setSelected(self, list);
  913. } else {
  914. list.hide();
  915. }
  916. }, 0);
  917. return;
  918. }
  919. switch (e.keyCode) {
  920. case 96: // numpad numerals
  921. case 97:
  922. case 98:
  923. case 99:
  924. case 100:
  925. case 101:
  926. case 102:
  927. case 103:
  928. case 104:
  929. case 105:
  930. case 48: // numerals
  931. case 49:
  932. case 50:
  933. case 51:
  934. case 52:
  935. case 53:
  936. case 54:
  937. case 55:
  938. case 56:
  939. case 57:
  940. case 65: // a
  941. case 77: // m
  942. case 80: // p
  943. case 186: // colon
  944. case 8: // backspace
  945. case 46: // delete
  946. if (settings.typeaheadHighlight) {
  947. _setSelected(self, list);
  948. } else {
  949. list.hide();
  950. }
  951. break;
  952. }
  953. }
  954. function _selectValue(self) {
  955. var settings = self.data("timepicker-settings");
  956. var list = self.data("timepicker-list");
  957. var timeValue = null;
  958. var cursor = list.find(".ui-timepicker-selected");
  959. if (cursor.hasClass("ui-timepicker-disabled")) {
  960. return false;
  961. }
  962. if (cursor.length) {
  963. // selected value found
  964. timeValue = cursor.data("time");
  965. }
  966. if (timeValue !== null) {
  967. if (typeof timeValue != "string") {
  968. timeValue = _int2time(timeValue, settings);
  969. }
  970. _setTimeValue(self, timeValue, "select");
  971. }
  972. return true;
  973. }
  974. function _int2duration(seconds, step) {
  975. seconds = Math.abs(seconds);
  976. var minutes = Math.round(seconds / 60),
  977. duration = [],
  978. hours,
  979. mins;
  980. if (minutes < 60) {
  981. // Only show (x mins) under 1 hour
  982. duration = [minutes, _lang.mins];
  983. } else {
  984. hours = Math.floor(minutes / 60);
  985. mins = minutes % 60;
  986. // Show decimal notation (eg: 1.5 hrs) for 30 minute steps
  987. if (step == 30 && mins == 30) {
  988. hours += _lang.decimal + 5;
  989. }
  990. duration.push(hours);
  991. duration.push(hours == 1 ? _lang.hr : _lang.hrs);
  992. // Show remainder minutes notation (eg: 1 hr 15 mins) for non-30 minute steps
  993. // and only if there are remainder minutes to show
  994. if (step != 30 && mins) {
  995. duration.push(mins);
  996. duration.push(_lang.mins);
  997. }
  998. }
  999. return duration.join(" ");
  1000. }
  1001. function _int2time(timeInt, settings) {
  1002. if (typeof timeInt != "number") {
  1003. return null;
  1004. }
  1005. var seconds = parseInt(timeInt % 60),
  1006. minutes = parseInt((timeInt / 60) % 60),
  1007. hours = parseInt((timeInt / (60 * 60)) % 24);
  1008. var time = new Date(1970, 0, 2, hours, minutes, seconds, 0);
  1009. if (isNaN(time.getTime())) {
  1010. return null;
  1011. }
  1012. if ($.type(settings.timeFormat) === "function") {
  1013. return settings.timeFormat(time);
  1014. }
  1015. var output = "";
  1016. var hour, code;
  1017. for (var i = 0; i < settings.timeFormat.length; i++) {
  1018. code = settings.timeFormat.charAt(i);
  1019. switch (code) {
  1020. case "a":
  1021. output += time.getHours() > 11 ? _lang.pm : _lang.am;
  1022. break;
  1023. case "A":
  1024. output += time.getHours() > 11 ? _lang.PM : _lang.AM;
  1025. break;
  1026. case "g":
  1027. hour = time.getHours() % 12;
  1028. output += hour === 0 ? "12" : hour;
  1029. break;
  1030. case "G":
  1031. hour = time.getHours();
  1032. if (timeInt === _ONE_DAY) hour = settings.show2400 ? 24 : 0;
  1033. output += hour;
  1034. break;
  1035. case "h":
  1036. hour = time.getHours() % 12;
  1037. if (hour !== 0 && hour < 10) {
  1038. hour = "0" + hour;
  1039. }
  1040. output += hour === 0 ? "12" : hour;
  1041. break;
  1042. case "H":
  1043. hour = time.getHours();
  1044. if (timeInt === _ONE_DAY) hour = settings.show2400 ? 24 : 0;
  1045. output += hour > 9 ? hour : "0" + hour;
  1046. break;
  1047. case "i":
  1048. var minutes = time.getMinutes();
  1049. output += minutes > 9 ? minutes : "0" + minutes;
  1050. break;
  1051. case "s":
  1052. seconds = time.getSeconds();
  1053. output += seconds > 9 ? seconds : "0" + seconds;
  1054. break;
  1055. case "\\":
  1056. // escape character; add the next character and skip ahead
  1057. i++;
  1058. output += settings.timeFormat.charAt(i);
  1059. break;
  1060. default:
  1061. output += code;
  1062. }
  1063. }
  1064. return output;
  1065. }
  1066. function _time2int(timeString, settings) {
  1067. if (timeString === "" || timeString === null) return null;
  1068. if (typeof timeString == "object") {
  1069. return (
  1070. timeString.getHours() * 3600 +
  1071. timeString.getMinutes() * 60 +
  1072. timeString.getSeconds()
  1073. );
  1074. }
  1075. if (typeof timeString != "string") {
  1076. return timeString;
  1077. }
  1078. timeString = timeString.toLowerCase().replace(/[\s\.]/g, "");
  1079. // if the last character is an "a" or "p", add the "m"
  1080. if (timeString.slice(-1) == "a" || timeString.slice(-1) == "p") {
  1081. timeString += "m";
  1082. }
  1083. var ampmRegex =
  1084. "(" +
  1085. _lang.am.replace(".", "") +
  1086. "|" +
  1087. _lang.pm.replace(".", "") +
  1088. "|" +
  1089. _lang.AM.replace(".", "") +
  1090. "|" +
  1091. _lang.PM.replace(".", "") +
  1092. ")?";
  1093. // try to parse time input
  1094. var pattern = new RegExp(
  1095. "^" +
  1096. ampmRegex +
  1097. "([0-9]?[0-9])\\W?([0-5][0-9])?\\W?([0-5][0-9])?" +
  1098. ampmRegex +
  1099. "$"
  1100. );
  1101. var time = timeString.match(pattern);
  1102. if (!time) {
  1103. return null;
  1104. }
  1105. var hour = parseInt(time[2] * 1, 10);
  1106. var ampm = time[1] || time[5];
  1107. var hours = hour;
  1108. var minutes = time[3] * 1 || 0;
  1109. var seconds = time[4] * 1 || 0;
  1110. if (hour <= 12 && ampm) {
  1111. var isPm = ampm == _lang.pm || ampm == _lang.PM;
  1112. if (hour == 12) {
  1113. hours = isPm ? 12 : 0;
  1114. } else {
  1115. hours = hour + (isPm ? 12 : 0);
  1116. }
  1117. } else if (settings) {
  1118. var t = hour * 3600 + minutes * 60 + seconds;
  1119. if (t >= _ONE_DAY + (settings.show2400 ? 1 : 0)) {
  1120. if (settings.wrapHours === false) {
  1121. return null;
  1122. }
  1123. hours = hour % 24;
  1124. }
  1125. }
  1126. var timeInt = hours * 3600 + minutes * 60 + seconds;
  1127. // if no am/pm provided, intelligently guess based on the scrollDefault
  1128. if (
  1129. hour < 12 &&
  1130. !ampm &&
  1131. settings &&
  1132. settings._twelveHourTime &&
  1133. settings.scrollDefault
  1134. ) {
  1135. var delta = timeInt - settings.scrollDefault();
  1136. if (delta < 0 && delta >= _ONE_DAY / -2) {
  1137. timeInt = (timeInt + _ONE_DAY / 2) % _ONE_DAY;
  1138. }
  1139. }
  1140. return timeInt;
  1141. }
  1142. function _pad2(n) {
  1143. return ("0" + n).slice(-2);
  1144. }
  1145. function _moduloSeconds(seconds, settings) {
  1146. if (seconds == _ONE_DAY && settings.show2400) {
  1147. return seconds;
  1148. }
  1149. return seconds % _ONE_DAY;
  1150. }
  1151. // Plugin entry
  1152. $.fn.timepicker = function(method) {
  1153. if (!this.length) return this;
  1154. if (methods[method]) {
  1155. // check if this element is a timepicker
  1156. if (!this.hasClass("ui-timepicker-input")) {
  1157. return this;
  1158. }
  1159. return methods[method].apply(
  1160. this,
  1161. Array.prototype.slice.call(arguments, 1)
  1162. );
  1163. } else if (typeof method === "object" || !method) {
  1164. return methods.init.apply(this, arguments);
  1165. } else {
  1166. $.error("Method " + method + " does not exist on jQuery.timepicker");
  1167. }
  1168. };
  1169. });