5
\$\begingroup\$

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.

I want you to help me write this code better.

What I have is basically a lot 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/

\$\endgroup\$
0
2
\$\begingroup\$

Spelling

  • pluing -> plugin

Remove commented out code

Once you feel the code is ready to be used, delete commented out code such as

    /* // This would be nice, but this plugin pattern isn't set up for public methods... 
    destroy: function() {
        console.log('destroying');
        this.$el.unbind('.timeMask');
    }*/

JSHint

You have a fiddle, try clicking the JsHint button, you will find a number of things to clean up.

  • Semicolon in front of (function ($, window, document, undefined) {
  • If you are using parseInt, you should provide the radix (10)
  • You have missing semicolons
  • You have an extra comma on }, // end init, this can break on older versions of IE

_getValueInts

You could use [].push here to make your code simpler, also you did not declare i.

    _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;
    },

becomes

    _getValueInts: function () {
        // Get array of number values    
        for (var i = 0, ints = []; i < this._val.length; i++) {
            if (parseInt(this._val[i],10) >= 0) {
                ints.push( parseInt(this._val[i],10);
            }
        }
        return ints;
    }

or even, if you are okay with dropping curlies and not using parseInt:

    _getValueInts: function () {
        // Get array of number values    
        for (var i = 0, ints = []; i < this._val.length; i++)
            if ( +this._val[i] >= 0 ) 
                ints.push( +this._val[i] );
        return ints;
    }

getTimeStr

There be bugs in this function:

} else if (n[1] >= 6) { // "17" => 05:00 pm

Will turn 13, 14 and 15 to 13AM, 14AM and 15AM, which obviously is impossible..

In general you should in this function move as quickly as possible into the variables hours and minutes and no longer mess around with that integer array, and then only move in 1 place from 17 -> 5PM.

Your requirements are so intense that I did not even try to rewrite this code, are you sure you're not overdoing it?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.