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/

share|improve this question

migrated from stackoverflow.com Feb 28 '12 at 18:50

Know someone who can answer? Share a link to this question via email, Google+, Twitter, or Facebook.

Your Answer

 
or
required, but never shown
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.