picker.time.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016
  1. /*!
  2. * Time picker for pickadate.js v3.6.4
  3. * http://amsul.github.io/pickadate.js/time.htm
  4. */
  5. (function ( factory ) {
  6. // AMD.
  7. if ( typeof define == 'function' && define.amd )
  8. define( ['./picker', 'jquery'], factory )
  9. // Node.js/browserify.
  10. else if ( typeof exports == 'object' )
  11. module.exports = factory( require('./picker.js'), require('jquery') )
  12. // Browser globals.
  13. else factory( Picker, jQuery )
  14. }(function( Picker, $ ) {
  15. /**
  16. * Globals and constants
  17. */
  18. var HOURS_IN_DAY = 24,
  19. MINUTES_IN_HOUR = 60,
  20. HOURS_TO_NOON = 12,
  21. MINUTES_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR,
  22. _ = Picker._
  23. /**
  24. * The time picker constructor
  25. */
  26. function TimePicker( picker, settings ) {
  27. var clock = this,
  28. elementValue = picker.$node[ 0 ].value,
  29. elementDataValue = picker.$node.data( 'value' ),
  30. valueString = elementDataValue || elementValue,
  31. formatString = elementDataValue ? settings.formatSubmit : settings.format
  32. clock.settings = settings
  33. clock.$node = picker.$node
  34. // The queue of methods that will be used to build item objects.
  35. clock.queue = {
  36. interval: 'i',
  37. min: 'measure create',
  38. max: 'measure create',
  39. now: 'now create',
  40. select: 'parse create validate',
  41. highlight: 'parse create validate',
  42. view: 'parse create validate',
  43. disable: 'deactivate',
  44. enable: 'activate'
  45. }
  46. // The component's item object.
  47. clock.item = {}
  48. clock.item.clear = null
  49. clock.item.interval = settings.interval || 30
  50. clock.item.disable = ( settings.disable || [] ).slice( 0 )
  51. clock.item.enable = -(function( collectionDisabled ) {
  52. return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1
  53. })( clock.item.disable )
  54. clock.
  55. set( 'min', settings.min ).
  56. set( 'max', settings.max ).
  57. set( 'now' )
  58. // When there’s a value, set the `select`, which in turn
  59. // also sets the `highlight` and `view`.
  60. if ( valueString ) {
  61. clock.set( 'select', valueString, {
  62. format: formatString
  63. })
  64. }
  65. // If there’s no value, default to highlighting “today”.
  66. else {
  67. clock.
  68. set( 'select', null ).
  69. set( 'highlight', clock.item.now )
  70. }
  71. // The keycode to movement mapping.
  72. clock.key = {
  73. 40: 1, // Down
  74. 38: -1, // Up
  75. 39: 1, // Right
  76. 37: -1, // Left
  77. go: function( timeChange ) {
  78. clock.set(
  79. 'highlight',
  80. clock.item.highlight.pick + timeChange * clock.item.interval,
  81. { interval: timeChange * clock.item.interval }
  82. )
  83. this.render()
  84. }
  85. }
  86. // Bind some picker events.
  87. picker.
  88. on( 'render', function() {
  89. var $pickerHolder = picker.$root.children(),
  90. $viewset = $pickerHolder.find( '.' + settings.klass.viewset ),
  91. vendors = function( prop ) {
  92. return ['webkit', 'moz', 'ms', 'o', ''].map(function( vendor ) {
  93. return ( vendor ? '-' + vendor + '-' : '' ) + prop
  94. })
  95. },
  96. animations = function( $el, state ) {
  97. vendors( 'transform' ).map(function( prop ) {
  98. $el.css( prop, state )
  99. })
  100. vendors( 'transition' ).map(function( prop ) {
  101. $el.css( prop, state )
  102. })
  103. }
  104. if ( $viewset.length ) {
  105. animations( $pickerHolder, 'none' )
  106. $pickerHolder[ 0 ].scrollTop = ~~$viewset.position().top - ( $viewset[ 0 ].clientHeight * 2 )
  107. animations( $pickerHolder, '' )
  108. }
  109. }, 1 ).
  110. on( 'open', function() {
  111. picker.$root.find( 'button' ).attr( 'disabled', false )
  112. }, 1 ).
  113. on( 'close', function() {
  114. picker.$root.find( 'button' ).attr( 'disabled', true )
  115. }, 1 )
  116. } //TimePicker
  117. /**
  118. * Set a timepicker item object.
  119. */
  120. TimePicker.prototype.set = function( type, value, options ) {
  121. var clock = this,
  122. clockItem = clock.item
  123. // If the value is `null` just set it immediately.
  124. if ( value === null ) {
  125. if ( type == 'clear' ) type = 'select'
  126. clockItem[ type ] = value
  127. return clock
  128. }
  129. // Otherwise go through the queue of methods, and invoke the functions.
  130. // Update this as the time unit, and set the final value as this item.
  131. // * In the case of `enable`, keep the queue but set `disable` instead.
  132. // And in the case of `flip`, keep the queue but set `enable` instead.
  133. clockItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = clock.queue[ type ].split( ' ' ).map( function( method ) {
  134. value = clock[ method ]( type, value, options )
  135. return value
  136. }).pop()
  137. // Check if we need to cascade through more updates.
  138. if ( type == 'select' ) {
  139. clock.set( 'highlight', clockItem.select, options )
  140. }
  141. else if ( type == 'highlight' ) {
  142. clock.set( 'view', clockItem.highlight, options )
  143. }
  144. else if ( type == 'interval' ) {
  145. clock.
  146. set( 'min', clockItem.min, options ).
  147. set( 'max', clockItem.max, options )
  148. }
  149. else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) {
  150. if ( clockItem.select && clock.disabled( clockItem.select ) ) {
  151. clock.set( 'select', value, options )
  152. }
  153. if ( clockItem.highlight && clock.disabled( clockItem.highlight ) ) {
  154. clock.set( 'highlight', value, options )
  155. }
  156. if ( type == 'min' ) {
  157. clock.set( 'max', clockItem.max, options )
  158. }
  159. }
  160. return clock
  161. } //TimePicker.prototype.set
  162. /**
  163. * Get a timepicker item object.
  164. */
  165. TimePicker.prototype.get = function( type ) {
  166. return this.item[ type ]
  167. } //TimePicker.prototype.get
  168. /**
  169. * Create a picker time object.
  170. */
  171. TimePicker.prototype.create = function( type, value, options ) {
  172. var clock = this
  173. // If there’s no value, use the type as the value.
  174. value = value === undefined ? type : value
  175. // If it’s a date object, convert it into an array.
  176. if ( _.isDate( value ) ) {
  177. value = [ value.getHours(), value.getMinutes() ]
  178. }
  179. // If it’s an object, use the “pick” value.
  180. if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) {
  181. value = value.pick
  182. }
  183. // If it’s an array, convert it into minutes.
  184. else if ( $.isArray( value ) ) {
  185. value = +value[ 0 ] * MINUTES_IN_HOUR + (+value[ 1 ])
  186. }
  187. // If no valid value is passed, set it to “now”.
  188. else if ( !_.isInteger( value ) ) {
  189. value = clock.now( type, value, options )
  190. }
  191. // If we’re setting the max, make sure it’s greater than the min.
  192. if ( type == 'max' && value < clock.item.min.pick ) {
  193. value += MINUTES_IN_DAY
  194. }
  195. // If the value doesn’t fall directly on the interval,
  196. // add one interval to indicate it as “passed”.
  197. if ( type != 'min' && type != 'max' && (value - clock.item.min.pick) % clock.item.interval !== 0 ) {
  198. value += clock.item.interval
  199. }
  200. // Normalize it into a “reachable” interval.
  201. value = clock.normalize( type, value, options )
  202. // Return the compiled object.
  203. return {
  204. // Divide to get hours from minutes.
  205. hour: ~~( HOURS_IN_DAY + value / MINUTES_IN_HOUR ) % HOURS_IN_DAY,
  206. // The remainder is the minutes.
  207. mins: ( MINUTES_IN_HOUR + value % MINUTES_IN_HOUR ) % MINUTES_IN_HOUR,
  208. // The time in total minutes.
  209. time: ( MINUTES_IN_DAY + value ) % MINUTES_IN_DAY,
  210. // Reference to the “relative” value to pick.
  211. pick: value % MINUTES_IN_DAY
  212. }
  213. } //TimePicker.prototype.create
  214. /**
  215. * Create a range limit object using an array, date object,
  216. * literal “true”, or integer relative to another time.
  217. */
  218. TimePicker.prototype.createRange = function( from, to ) {
  219. var clock = this,
  220. createTime = function( time ) {
  221. if ( time === true || $.isArray( time ) || _.isDate( time ) ) {
  222. return clock.create( time )
  223. }
  224. return time
  225. }
  226. // Create objects if possible.
  227. if ( !_.isInteger( from ) ) {
  228. from = createTime( from )
  229. }
  230. if ( !_.isInteger( to ) ) {
  231. to = createTime( to )
  232. }
  233. // Create relative times.
  234. if ( _.isInteger( from ) && $.isPlainObject( to ) ) {
  235. from = [ to.hour, to.mins + ( from * clock.settings.interval ) ];
  236. }
  237. else if ( _.isInteger( to ) && $.isPlainObject( from ) ) {
  238. to = [ from.hour, from.mins + ( to * clock.settings.interval ) ];
  239. }
  240. return {
  241. from: createTime( from ),
  242. to: createTime( to )
  243. }
  244. } //TimePicker.prototype.createRange
  245. /**
  246. * Check if a time unit falls within a time range object.
  247. */
  248. TimePicker.prototype.withinRange = function( range, timeUnit ) {
  249. range = this.createRange(range.from, range.to)
  250. return timeUnit.pick >= range.from.pick && timeUnit.pick <= range.to.pick
  251. }
  252. /**
  253. * Check if two time range objects overlap.
  254. */
  255. TimePicker.prototype.overlapRanges = function( one, two ) {
  256. var clock = this
  257. // Convert the ranges into comparable times.
  258. one = clock.createRange( one.from, one.to )
  259. two = clock.createRange( two.from, two.to )
  260. return clock.withinRange( one, two.from ) || clock.withinRange( one, two.to ) ||
  261. clock.withinRange( two, one.from ) || clock.withinRange( two, one.to )
  262. }
  263. /**
  264. * Get the time relative to now.
  265. */
  266. TimePicker.prototype.now = function( type, value/*, options*/ ) {
  267. var interval = this.item.interval,
  268. date = new Date(),
  269. nowMinutes = date.getHours() * MINUTES_IN_HOUR + date.getMinutes(),
  270. isValueInteger = _.isInteger( value ),
  271. isBelowInterval
  272. // Make sure “now” falls within the interval range.
  273. nowMinutes -= nowMinutes % interval
  274. // Check if the difference is less than the interval itself.
  275. isBelowInterval = value < 0 && interval * value + nowMinutes <= -interval
  276. // Add an interval because the time has “passed”.
  277. nowMinutes += type == 'min' && isBelowInterval ? 0 : interval
  278. // If the value is a number, adjust by that many intervals.
  279. if ( isValueInteger ) {
  280. nowMinutes += interval * (
  281. isBelowInterval && type != 'max' ?
  282. value + 1 :
  283. value
  284. )
  285. }
  286. // Return the final calculation.
  287. return nowMinutes
  288. } //TimePicker.prototype.now
  289. /**
  290. * Normalize minutes to be “reachable” based on the min and interval.
  291. */
  292. TimePicker.prototype.normalize = function( type, value/*, options*/ ) {
  293. var interval = this.item.interval,
  294. minTime = this.item.min && this.item.min.pick || 0
  295. // If setting min time, don’t shift anything.
  296. // Otherwise get the value and min difference and then
  297. // normalize the difference with the interval.
  298. value -= type == 'min' ? 0 : ( value - minTime ) % interval
  299. // Return the adjusted value.
  300. return value
  301. } //TimePicker.prototype.normalize
  302. /**
  303. * Measure the range of minutes.
  304. */
  305. TimePicker.prototype.measure = function( type, value, options ) {
  306. var clock = this
  307. // If it’s anything false-y, set it to the default.
  308. if ( !value ) {
  309. value = type == 'min' ? [ 0, 0 ] : [ HOURS_IN_DAY - 1, MINUTES_IN_HOUR - 1 ]
  310. }
  311. // If it’s a string, parse it.
  312. if ( typeof value == 'string' ) {
  313. value = clock.parse( type, value )
  314. }
  315. // If it’s a literal true, or an integer, make it relative to now.
  316. else if ( value === true || _.isInteger( value ) ) {
  317. value = clock.now( type, value, options )
  318. }
  319. // If it’s an object already, just normalize it.
  320. else if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) {
  321. value = clock.normalize( type, value.pick, options )
  322. }
  323. return value
  324. } ///TimePicker.prototype.measure
  325. /**
  326. * Validate an object as enabled.
  327. */
  328. TimePicker.prototype.validate = function( type, timeObject, options ) {
  329. var clock = this,
  330. interval = options && options.interval ? options.interval : clock.item.interval
  331. // Check if the object is disabled.
  332. if ( clock.disabled( timeObject ) ) {
  333. // Shift with the interval until we reach an enabled time.
  334. timeObject = clock.shift( timeObject, interval )
  335. }
  336. // Scope the object into range.
  337. timeObject = clock.scope( timeObject )
  338. // Do a second check to see if we landed on a disabled min/max.
  339. // In that case, shift using the opposite interval as before.
  340. if ( clock.disabled( timeObject ) ) {
  341. timeObject = clock.shift( timeObject, interval * -1 )
  342. }
  343. // Return the final object.
  344. return timeObject
  345. } //TimePicker.prototype.validate
  346. /**
  347. * Check if an object is disabled.
  348. */
  349. TimePicker.prototype.disabled = function( timeToVerify ) {
  350. var clock = this,
  351. // Filter through the disabled times to check if this is one.
  352. isDisabledMatch = clock.item.disable.filter( function( timeToDisable ) {
  353. // If the time is a number, match the hours.
  354. if ( _.isInteger( timeToDisable ) ) {
  355. return timeToVerify.hour == timeToDisable
  356. }
  357. // If it’s an array, create the object and match the times.
  358. if ( $.isArray( timeToDisable ) || _.isDate( timeToDisable ) ) {
  359. return timeToVerify.pick == clock.create( timeToDisable ).pick
  360. }
  361. // If it’s an object, match a time within the “from” and “to” range.
  362. if ( $.isPlainObject( timeToDisable ) ) {
  363. return clock.withinRange( timeToDisable, timeToVerify )
  364. }
  365. })
  366. // If this time matches a disabled time, confirm it’s not inverted.
  367. isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( timeToDisable ) {
  368. return $.isArray( timeToDisable ) && timeToDisable[2] == 'inverted' ||
  369. $.isPlainObject( timeToDisable ) && timeToDisable.inverted
  370. }).length
  371. // If the clock is "enabled" flag is flipped, flip the condition.
  372. return clock.item.enable === -1 ? !isDisabledMatch : isDisabledMatch ||
  373. timeToVerify.pick < clock.item.min.pick ||
  374. timeToVerify.pick > clock.item.max.pick
  375. } //TimePicker.prototype.disabled
  376. /**
  377. * Shift an object by an interval until we reach an enabled object.
  378. */
  379. TimePicker.prototype.shift = function( timeObject, interval ) {
  380. var clock = this,
  381. minLimit = clock.item.min.pick,
  382. maxLimit = clock.item.max.pick/*,
  383. safety = 1000*/
  384. interval = interval || clock.item.interval
  385. // Keep looping as long as the time is disabled.
  386. while ( /*safety &&*/ clock.disabled( timeObject ) ) {
  387. /*safety -= 1
  388. if ( !safety ) {
  389. throw 'Fell into an infinite loop while shifting to ' + timeObject.hour + ':' + timeObject.mins + '.'
  390. }*/
  391. // Increase/decrease the time by the interval and keep looping.
  392. timeObject = clock.create( timeObject.pick += interval )
  393. // If we've looped beyond the limits, break out of the loop.
  394. if ( timeObject.pick <= minLimit || timeObject.pick >= maxLimit ) {
  395. break
  396. }
  397. }
  398. // Return the final object.
  399. return timeObject
  400. } //TimePicker.prototype.shift
  401. /**
  402. * Scope an object to be within range of min and max.
  403. */
  404. TimePicker.prototype.scope = function( timeObject ) {
  405. var minLimit = this.item.min.pick,
  406. maxLimit = this.item.max.pick
  407. return this.create( timeObject.pick > maxLimit ? maxLimit : timeObject.pick < minLimit ? minLimit : timeObject )
  408. } //TimePicker.prototype.scope
  409. /**
  410. * Parse a string into a usable type.
  411. */
  412. TimePicker.prototype.parse = function( type, value, options ) {
  413. var hour, minutes, isPM, item, parseValue,
  414. clock = this,
  415. parsingObject = {}
  416. // If it’s already parsed, we’re good.
  417. if ( !value || typeof value != 'string' ) {
  418. return value
  419. }
  420. // We need a `.format` to parse the value with.
  421. if ( !( options && options.format ) ) {
  422. options = options || {}
  423. options.format = clock.settings.format
  424. }
  425. // Convert the format into an array and then map through it.
  426. clock.formats.toArray( options.format ).map( function( label ) {
  427. var
  428. substring,
  429. // Grab the formatting label.
  430. formattingLabel = clock.formats[ label ],
  431. // The format length is from the formatting label function or the
  432. // label length without the escaping exclamation (!) mark.
  433. formatLength = formattingLabel ?
  434. _.trigger( formattingLabel, clock, [ value, parsingObject ] ) :
  435. label.replace( /^!/, '' ).length
  436. // If there's a format label, split the value up to the format length.
  437. // Then add it to the parsing object with appropriate label.
  438. if ( formattingLabel ) {
  439. substring = value.substr( 0, formatLength )
  440. parsingObject[ label ] = substring.match(/^\d+$/) ? +substring : substring
  441. }
  442. // Update the time value as the substring from format length to end.
  443. value = value.substr( formatLength )
  444. })
  445. // Grab the hour and minutes from the parsing object.
  446. for ( item in parsingObject ) {
  447. parseValue = parsingObject[item]
  448. if ( _.isInteger(parseValue) ) {
  449. if ( item.match(/^(h|hh)$/i) ) {
  450. hour = parseValue
  451. if ( item == 'h' || item == 'hh' ) {
  452. hour %= 12
  453. }
  454. }
  455. else if ( item == 'i' ) {
  456. minutes = parseValue
  457. }
  458. }
  459. else if ( item.match(/^a$/i) && parseValue.match(/^p/i) && ('h' in parsingObject || 'hh' in parsingObject) ) {
  460. isPM = true
  461. }
  462. }
  463. // Calculate it in minutes and return.
  464. return (isPM ? hour + 12 : hour) * MINUTES_IN_HOUR + minutes
  465. } //TimePicker.prototype.parse
  466. /**
  467. * Various formats to display the object in.
  468. */
  469. TimePicker.prototype.formats = {
  470. h: function( string, timeObject ) {
  471. // If there's string, then get the digits length.
  472. // Otherwise return the selected hour in "standard" format.
  473. return string ? _.digits( string ) : timeObject.hour % HOURS_TO_NOON || HOURS_TO_NOON
  474. },
  475. hh: function( string, timeObject ) {
  476. // If there's a string, then the length is always 2.
  477. // Otherwise return the selected hour in "standard" format with a leading zero.
  478. return string ? 2 : _.lead( timeObject.hour % HOURS_TO_NOON || HOURS_TO_NOON )
  479. },
  480. H: function( string, timeObject ) {
  481. // If there's string, then get the digits length.
  482. // Otherwise return the selected hour in "military" format as a string.
  483. return string ? _.digits( string ) : '' + ( timeObject.hour % 24 )
  484. },
  485. HH: function( string, timeObject ) {
  486. // If there's string, then get the digits length.
  487. // Otherwise return the selected hour in "military" format with a leading zero.
  488. return string ? _.digits( string ) : _.lead( timeObject.hour % 24 )
  489. },
  490. i: function( string, timeObject ) {
  491. // If there's a string, then the length is always 2.
  492. // Otherwise return the selected minutes.
  493. return string ? 2 : _.lead( timeObject.mins )
  494. },
  495. a: function( string, timeObject ) {
  496. // If there's a string, then the length is always 4.
  497. // Otherwise check if it's more than "noon" and return either am/pm.
  498. return string ? 4 : MINUTES_IN_DAY / 2 > timeObject.time % MINUTES_IN_DAY ? 'a.m.' : 'p.m.'
  499. },
  500. A: function( string, timeObject ) {
  501. // If there's a string, then the length is always 2.
  502. // Otherwise check if it's more than "noon" and return either am/pm.
  503. return string ? 2 : MINUTES_IN_DAY / 2 > timeObject.time % MINUTES_IN_DAY ? 'AM' : 'PM'
  504. },
  505. // Create an array by splitting the formatting string passed.
  506. toArray: function( formatString ) { return formatString.split( /(h{1,2}|H{1,2}|i|a|A|!.)/g ) },
  507. // Format an object into a string using the formatting options.
  508. toString: function ( formatString, itemObject ) {
  509. var clock = this
  510. return clock.formats.toArray( formatString ).map( function( label ) {
  511. return _.trigger( clock.formats[ label ], clock, [ 0, itemObject ] ) || label.replace( /^!/, '' )
  512. }).join( '' )
  513. }
  514. } //TimePicker.prototype.formats
  515. /**
  516. * Check if two time units are the exact.
  517. */
  518. TimePicker.prototype.isTimeExact = function( one, two ) {
  519. var clock = this
  520. // When we’re working with minutes, do a direct comparison.
  521. if (
  522. ( _.isInteger( one ) && _.isInteger( two ) ) ||
  523. ( typeof one == 'boolean' && typeof two == 'boolean' )
  524. ) {
  525. return one === two
  526. }
  527. // When we’re working with time representations, compare the “pick” value.
  528. if (
  529. ( _.isDate( one ) || $.isArray( one ) ) &&
  530. ( _.isDate( two ) || $.isArray( two ) )
  531. ) {
  532. return clock.create( one ).pick === clock.create( two ).pick
  533. }
  534. // When we’re working with range objects, compare the “from” and “to”.
  535. if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
  536. return clock.isTimeExact( one.from, two.from ) && clock.isTimeExact( one.to, two.to )
  537. }
  538. return false
  539. }
  540. /**
  541. * Check if two time units overlap.
  542. */
  543. TimePicker.prototype.isTimeOverlap = function( one, two ) {
  544. var clock = this
  545. // When we’re working with an integer, compare the hours.
  546. if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) {
  547. return one === clock.create( two ).hour
  548. }
  549. if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) {
  550. return two === clock.create( one ).hour
  551. }
  552. // When we’re working with range objects, check if the ranges overlap.
  553. if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
  554. return clock.overlapRanges( one, two )
  555. }
  556. return false
  557. }
  558. /**
  559. * Flip the “enabled” state.
  560. */
  561. TimePicker.prototype.flipEnable = function(val) {
  562. var itemObject = this.item
  563. itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1)
  564. }
  565. /**
  566. * Mark a collection of times as “disabled”.
  567. */
  568. TimePicker.prototype.deactivate = function( type, timesToDisable ) {
  569. var clock = this,
  570. disabledItems = clock.item.disable.slice(0)
  571. // If we’re flipping, that’s all we need to do.
  572. if ( timesToDisable == 'flip' ) {
  573. clock.flipEnable()
  574. }
  575. else if ( timesToDisable === false ) {
  576. clock.flipEnable(1)
  577. disabledItems = []
  578. }
  579. else if ( timesToDisable === true ) {
  580. clock.flipEnable(-1)
  581. disabledItems = []
  582. }
  583. // Otherwise go through the times to disable.
  584. else {
  585. timesToDisable.map(function( unitToDisable ) {
  586. var matchFound
  587. // When we have disabled items, check for matches.
  588. // If something is matched, immediately break out.
  589. for ( var index = 0; index < disabledItems.length; index += 1 ) {
  590. if ( clock.isTimeExact( unitToDisable, disabledItems[index] ) ) {
  591. matchFound = true
  592. break
  593. }
  594. }
  595. // If nothing was found, add the validated unit to the collection.
  596. if ( !matchFound ) {
  597. if (
  598. _.isInteger( unitToDisable ) ||
  599. _.isDate( unitToDisable ) ||
  600. $.isArray( unitToDisable ) ||
  601. ( $.isPlainObject( unitToDisable ) && unitToDisable.from && unitToDisable.to )
  602. ) {
  603. disabledItems.push( unitToDisable )
  604. }
  605. }
  606. })
  607. }
  608. // Return the updated collection.
  609. return disabledItems
  610. } //TimePicker.prototype.deactivate
  611. /**
  612. * Mark a collection of times as “enabled”.
  613. */
  614. TimePicker.prototype.activate = function( type, timesToEnable ) {
  615. var clock = this,
  616. disabledItems = clock.item.disable,
  617. disabledItemsCount = disabledItems.length
  618. // If we’re flipping, that’s all we need to do.
  619. if ( timesToEnable == 'flip' ) {
  620. clock.flipEnable()
  621. }
  622. else if ( timesToEnable === true ) {
  623. clock.flipEnable(1)
  624. disabledItems = []
  625. }
  626. else if ( timesToEnable === false ) {
  627. clock.flipEnable(-1)
  628. disabledItems = []
  629. }
  630. // Otherwise go through the disabled times.
  631. else {
  632. timesToEnable.map(function( unitToEnable ) {
  633. var matchFound,
  634. disabledUnit,
  635. index,
  636. isRangeMatched
  637. // Go through the disabled items and try to find a match.
  638. for ( index = 0; index < disabledItemsCount; index += 1 ) {
  639. disabledUnit = disabledItems[index]
  640. // When an exact match is found, remove it from the collection.
  641. if ( clock.isTimeExact( disabledUnit, unitToEnable ) ) {
  642. matchFound = disabledItems[index] = null
  643. isRangeMatched = true
  644. break
  645. }
  646. // When an overlapped match is found, add the “inverted” state to it.
  647. else if ( clock.isTimeOverlap( disabledUnit, unitToEnable ) ) {
  648. if ( $.isPlainObject( unitToEnable ) ) {
  649. unitToEnable.inverted = true
  650. matchFound = unitToEnable
  651. }
  652. else if ( $.isArray( unitToEnable ) ) {
  653. matchFound = unitToEnable
  654. if ( !matchFound[2] ) matchFound.push( 'inverted' )
  655. }
  656. else if ( _.isDate( unitToEnable ) ) {
  657. matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ]
  658. }
  659. break
  660. }
  661. }
  662. // If a match was found, remove a previous duplicate entry.
  663. if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
  664. if ( clock.isTimeExact( disabledItems[index], unitToEnable ) ) {
  665. disabledItems[index] = null
  666. break
  667. }
  668. }
  669. // In the event that we’re dealing with an overlap of range times,
  670. // make sure there are no “inverted” times because of it.
  671. if ( isRangeMatched ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
  672. if ( clock.isTimeOverlap( disabledItems[index], unitToEnable ) ) {
  673. disabledItems[index] = null
  674. break
  675. }
  676. }
  677. // If something is still matched, add it into the collection.
  678. if ( matchFound ) {
  679. disabledItems.push( matchFound )
  680. }
  681. })
  682. }
  683. // Return the updated collection.
  684. return disabledItems.filter(function( val ) { return val != null })
  685. } //TimePicker.prototype.activate
  686. /**
  687. * The division to use for the range intervals.
  688. */
  689. TimePicker.prototype.i = function( type, value/*, options*/ ) {
  690. return _.isInteger( value ) && value > 0 ? value : this.item.interval
  691. }
  692. /**
  693. * Create a string for the nodes in the picker.
  694. */
  695. TimePicker.prototype.nodes = function( isOpen ) {
  696. var
  697. clock = this,
  698. settings = clock.settings,
  699. selectedObject = clock.item.select,
  700. highlightedObject = clock.item.highlight,
  701. viewsetObject = clock.item.view,
  702. disabledCollection = clock.item.disable
  703. return _.node(
  704. 'ul',
  705. _.group({
  706. min: clock.item.min.pick,
  707. max: clock.item.max.pick,
  708. i: clock.item.interval,
  709. node: 'li',
  710. item: function( loopedTime ) {
  711. loopedTime = clock.create( loopedTime )
  712. var timeMinutes = loopedTime.pick,
  713. isSelected = selectedObject && selectedObject.pick == timeMinutes,
  714. isHighlighted = highlightedObject && highlightedObject.pick == timeMinutes,
  715. isDisabled = disabledCollection && clock.disabled( loopedTime ),
  716. formattedTime = _.trigger( clock.formats.toString, clock, [ settings.format, loopedTime ] )
  717. return [
  718. _.trigger( clock.formats.toString, clock, [ _.trigger( settings.formatLabel, clock, [ loopedTime ] ) || settings.format, loopedTime ] ),
  719. (function( klasses ) {
  720. if ( isSelected ) {
  721. klasses.push( settings.klass.selected )
  722. }
  723. if ( isHighlighted ) {
  724. klasses.push( settings.klass.highlighted )
  725. }
  726. if ( viewsetObject && viewsetObject.pick == timeMinutes ) {
  727. klasses.push( settings.klass.viewset )
  728. }
  729. if ( isDisabled ) {
  730. klasses.push( settings.klass.disabled )
  731. }
  732. return klasses.join( ' ' )
  733. })( [ settings.klass.listItem ] ),
  734. 'data-pick=' + loopedTime.pick + ' ' + _.ariaAttr({
  735. role: 'option',
  736. label: formattedTime,
  737. selected: isSelected && clock.$node.val() === formattedTime ? true : null,
  738. activedescendant: isHighlighted ? true : null,
  739. disabled: isDisabled ? true : null
  740. })
  741. ]
  742. }
  743. }) +
  744. // * For Firefox forms to submit, make sure to set the button’s `type` attribute as “button”.
  745. _.node(
  746. 'li',
  747. _.node(
  748. 'button',
  749. settings.clear,
  750. settings.klass.buttonClear,
  751. 'type=button data-clear=1' + ( isOpen ? '' : ' disabled' ) + ' ' +
  752. _.ariaAttr({ controls: clock.$node[0].id })
  753. ),
  754. '', _.ariaAttr({ role: 'presentation' })
  755. ),
  756. settings.klass.list,
  757. _.ariaAttr({ role: 'listbox', controls: clock.$node[0].id })
  758. )
  759. } //TimePicker.prototype.nodes
  760. /**
  761. * Extend the picker to add the component with the defaults.
  762. */
  763. TimePicker.defaults = (function( prefix ) {
  764. return {
  765. // Clear
  766. clear: 'Clear',
  767. // The format to show on the `input` element
  768. format: 'h:i A',
  769. // The interval between each time
  770. interval: 30,
  771. // Picker close behavior
  772. closeOnSelect: true,
  773. closeOnClear: true,
  774. // Update input value on select/clear
  775. updateInput: true,
  776. // Classes
  777. klass: {
  778. picker: prefix + ' ' + prefix + '--time',
  779. holder: prefix + '__holder',
  780. list: prefix + '__list',
  781. listItem: prefix + '__list-item',
  782. disabled: prefix + '__list-item--disabled',
  783. selected: prefix + '__list-item--selected',
  784. highlighted: prefix + '__list-item--highlighted',
  785. viewset: prefix + '__list-item--viewset',
  786. now: prefix + '__list-item--now',
  787. buttonClear: prefix + '__button--clear'
  788. }
  789. }
  790. })( Picker.klasses().picker )
  791. /**
  792. * Extend the picker to add the time picker.
  793. */
  794. Picker.extend( 'pickatime', TimePicker )
  795. }));