To review, the current TimePicker has this implementation for getting the current value of the widget:
_value: function() { var hour = this.$hour.val(), min = this.$minute.val(), ampm = this.$ampm.val(); return hour + ':' + min + ' ' + ampm; }
And this chunk of code handles parsing an incoming value and setting each spinner:
_parse: function( val ) { var parts = val.split( /[: ]/ ), hour, min, ampm; if ( parts.length < 2 ) return; hour = parts[0]; min = parts[1]; ampm = parts[2]; this.$hour.paddedspinner( 'value', +hour ); this.$minute.paddedspinner( 'value', +min ); this.$ampm.ampmspinner( 'value', ampm == 'AM' ? 0 : 1 ); }
The goal is to retain those sections of code but 1) handle a 24-hour clock and 2) detect Moment and use it to both parse the incoming value or create a Moment instance to return the current value of the widget. Before doing that, we need to know the format to use for the widget. I decided to use a format string that aligns with Moment to determine whether to use a 12 or 24 hour clock:
$.widget('osb.timepicker', { options: { format: 'hh:mm A' // 12-hour format // HH:mm triggers 24-hour }, _init: function () { ... // Set a flag to check through out code this.hour24 = ( this.options.format.indexOf( 'HH' ) > -1 ); ... } ... }
Near the top of the
_init
function, that format string is parsed to determine the clock format. With that flag set, we can add blocks around every spot that has to use the AM/PM spinner and decides how to interpret the hours spinner. Getting the current value isn't too bad - just a little more code to check the clock format and switch between Moment and a basic string:_value: function() { var hour = +this.$hour.val(), min = +this.$minute.val(), ampm = this.hour24 ? '' : this.$ampm.val(), val; if ( window.moment ) { // MomentJS included hour = ( ampm == 'PM' && hour < 12 ? hour + 12 : hour ); val = moment({ hour: hour, minute: min }); } else { // Fallback to simple string only val = hour + ':' + min; if ( !this.hour24 ) val += ' ' + ampm; } return val; }
When creating the Moment instance, you can specify the hours and minutes. Hours need to be in the 24-hour clock format so a little conversion is necessary if the widget is operating in 12-hour clock mode.
Setting the value is a little longer since I wanted to also accept a Javascript Date object. The constructor for Moment only needs the format string if the time is a string, otherwise, the second argument should be dropped. Additionally, the same conversion from 24-hour to 12-hour clock mode is necessary after calling the Moment
hour()
function:_parse: function( val ) { var tm, parts, hour, min, ampm; if ( window.moment ) { // MomentJS included if ( val instanceof Date ) tm = moment( val ); else tm = moment( val, this.options.format ); if ( !tm.isValid() ) return; hour = tm.hour(); min = tm.minute(); if ( !this.hour24 ) { ampm = hour >= 12 ? 'PM' : 'AM'; hour = hour > 12 ? hour - 12 : hour } } else { // Fallback to simple handling parts = val.split( /[: ]/ ); if ( parts.length < 2 ) return; hour = parts[0]; min = parts[1]; if ( !this.hour24 ) { ampm = parts[2] || 'AM'; } } this.$hour.paddedspinner( 'value', +hour ); this.$minute.paddedspinner( 'value', +min ); if ( !this.hour24 ) { this.$ampm.ampmspinner( 'value', ampm == 'AM' ? 0 : 1 ); } }
Again, the original string parser remains such that, if Moment is not present, it will be used to find the components of the time. There are a few other changes that were required for switching between the two clock formats which I did not show the code for here. These dealt with rendering the spinners (don't need AM/PM in 24-hour mode) and rolling over the hours properly.
The advantage of including Moment is that the library provides utilities to work with the the output from the picker in a variety of ways including reformatting the time and manipulating its value:
$( '#picker' ).timepicker(); // Can pass in a Date, String, or Moment instance $( '#picker' ).timepicker( 'value', '8:25 AM' ); // time is a MomentJS instance time = $( '#picker' ).timepicker( 'value' ); time.format( 'HH:mm' ); // 08:25 time.add( 'm', 5 ); time.format( 'h:mm A' ); // 8:30 AM
These changes are a nice improvement to the widget. While probably not as robust as it could be, its definitely a step in the right direction. The updated widget and examples are available on my jQuery UI extensions project at GitHub.