I've been searching for a suitable plugin for an input mask for a time field. There are some inherent difficulties with masking a time input, while giving the user the most flexibility.
I have tried to recreate what Google Calendar uses for their time inputs, minus the drop down select. What I've made seems to work, but it's definitely missing a bit of elegance, of bit of beauty, a bit of... je ne sais quoi.
So my challenge for you is to write this code better.
What I have is basically a big ol' bunch of logic statements. I wrote down on a piece of paper all the possible ways I could enter a time, and how I would want to plugin to translate that into a standard "00:00 xm" format.
Here is that list:
- abc => invalid
- abc1:23 => "1:23am"
- 123p => "1:23pm"
- 1 => "1:00am"
- 12 => "12:00am"
- 91 => "9:10am"
- 05 => "5:00pm"
- 123 => "1:23am"
- 170 => invalid
- 013 => invalid
- 1234 => "12:34am"
- 1934 => "7:34pm"
- 9123 => invalid
- 00:12 => "12:12am"
- 1294 => invalid
With that out of the way... here's the plugin code:
/*!
* jQuery lightweight plugin boilerplate
* Original author: @ajpiano
* Further changes, comments: @addyosmani
* Licensed under the MIT license
*/
;(function ( $, window, document, undefined ) {
// Create the defaults once
var pluginName = 'timeMask',
defaults = {
propertyName: "value"
};
// The actual plugin constructor
function Plugin( element, options ) {
this.$el = $(element);
this.options = $.extend( {}, defaults, options) ;
this._defaults = defaults;
this._name = pluginName;
this._val = this.$el.val();
this.init();
}
// retruns array of numbers in a string
Plugin.prototype = {
_getValueInts: function() {
var ints=[],
j =0;
// Get array of number values
for(i=0; i<this._val.length; i++) {
if(parseInt(this._val[i]) >= 0) {
ints[j] = parseInt(this._val[i]);
j++;
}
}
return ints;
},
_isPM: function() {
return (this._val.search(/p/gi) >= 0);
},
_setInvalid: function() {
this.$el.addClass('invalid-time');
},
_rmInvalid: function() {
this.$el.removeClass('invalid-time');
},
getTimeStr: function() {
var n = this._getValueInts(),
pm = this._isPM(), // Defaults to false (am)
time = [0, 0, 0, 0]; // 00:00
// No n -> invalid
if(n.length < 1) {
return false;
}
// Set time array
else if(n.length == 1) {
time = [0, n[0], 0, 0];
}
else if (n.length == 2) {
if(n[0] > 1) { // "91" => 09:10, 21 => 02:10
time = [0, n[0], n[1], 0]
}
else if(n[1] >= 6) { // "17" => 05:00 pm
time = [0, n[1]-2, 0, 0];
pm = true;
}
else { // "12" => 12:00, "05" => 05:00pm
time = [n[0], n[1], 0, 0];
}
}
else if (n.length == 3) {
if(n[1] >= 6) { // "170" => invalid
return false;
}
else if (n[0] < 1) { // "012" => invalid
return false;
}
else { // "123" => 1:23
time = [0, n[0], n[1], n[2]];
}
}
else if (n.length == 4) {
var hours = n[0]*10 + n[1];
if (n[2] >= 6) { // 12:95
return false;
}
else if(hours > 24) {
return false;
}
else if(hours > 12) { // "2312" => 11:12pm; hours = 11
hours = hours-12;
pm = true
if(hours >= 10) {
time = [1, hours-10, n[2], n[3]];
}
else {
time = [0, hours, n[2], n[3]];
}
}
else if(n[0] == 0 && n[1] == 0) { // "00:12" => 12:12 am
time = [1, 2, n[2], n[3]];
pm = false;
}
else {
time = [n[0], n[1], n[2], n[3]];
}
}
return "" + ((time[0]==0)? "":time[0]) + time[1] + ":" + time[2] + time[3] + ((!pm)? "am": "pm");
}, // end getTimeStr()
_setVal: function(newVal) {
this.val = newVal;
this.$el.val(newVal);
},
init: function() {
var _this = this;
this.$el.bind({
'focus.timeMask': function() {
_this._rmInvalid();
},
'blur.timeMask': function() {
_this._val = _this.$el.val();
var timeStr = _this.getTimeStr()
if(!timeStr) {
_this._setInvalid();
}
else {
_this._setVal(timeStr);
}
}
}); // end this$el.bind
}, // end init
/* // This would be nice, but this plugin pattern isn't set up for public methods...
destroy: function() {
console.log('destroying');
this.$el.unbind('.timeMask');
}*/
}; // end Plugin.prototype
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[pluginName] = function ( options ) {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName,
new Plugin( this, options ));
}
});
}
})( jQuery, window, document );
I appreciate any and all public rippings/humiliation/etc regarding my code. I'm a bit of an amateur at plugins, and I'm trying to get better at writing well structured code.
On jsFiddle: http://jsfiddle.net/gq8sQ/