21 Jul
15
Co-owner of Visuality

This won't be any deep-down post about general use of javascript libraries as title might suggest. I just want to show one particular problem that I had and how I found solution, so hopefully it will help someone else.

Going straight to the issue. One of our projects is about booking services like haircut or swedish massage, whatever that is. On the other side there is merchant that can specify in what time his employees work. Merchant can set start and finish hour per day. To make this work, we are using timepicker plugin and inputmask plugin. Timepicker generates dropdown with possible hours, but merchant can also input custom hour and inputmask is used to valid that input.

Last week we got a bug report that this timepicker is not working in Safari browser. After clicking on particular hour dropdown was disappearing but the value was not set.

First try: jquery-timepicker plugin

So my first guess was that there is a bug in timepicker. I've updated it to the newest version, but it didn't solved a problem. I've started looking into the source code, near line where value was set and added some console logs there:

function _setTimeValue(self, value, source)
{
  if (self.is('input')) {
    self.val(value);
    console.log('value to set', value);
    console.log('actual value', self.val());

    var settings = self.data('timepicker-settings');
    if (settings.useSelect && source != 'select' && source != 'initial') {
      self.data('timepicker-list').val(_roundAndFormatTime(_time2int(value), settings));
    }
  }

  // code omitted for brevity
}

So I was expecting to see self.val() to be equal value, but it was still the previous value for that field. The problem was not in jquery-timepicker.

Second try: inputmask plugin

So another guess was that there must be something wrong with inputmask. Temporarily commenting it out fixed my problem. I've started digging into the source code and found function that is used to override getter and setter for provided field. This function is pretty complicated, handling a lot of cases for different browsers. In case of Safari, PatchValhook function was called.

function patchValueProperty(npt) {
  var valueGet;
  var valueSet;

  function PatchValhook(type) {
    if ($.valHooks[type] == undefined || $.valHooks[type].inputmaskpatch != true) {
      var valhookGet = $.valHooks[type] && $.valHooks[type].get ? $.valHooks[type].get : function(elem) {
        return elem.value;
      };
      var valhookSet = $.valHooks[type] && $.valHooks[type].set ? $.valHooks[type].set : function(elem, value) {
        elem.value = value;
        return elem;
      };

      $.valHooks[type] = {
        get: function(elem) {
          var $elem = $(elem);
          if (elem.inputmask) {
            if (elem.inputmask.opts.autoUnmask)
              return elem.inputmask.unmaskedvalue();
            else {
              var result = valhookGet(elem),
                maskset = elem.inputmask.maskset,
                bufferTemplate = maskset['_buffer'];
              bufferTemplate = bufferTemplate ? bufferTemplate.join('') : '';
              return result != bufferTemplate ? result : '';
            }
          } else return valhookGet(elem);
        },
        set: function(elem, value) {
          var $elem = $(elem),
            result;
          result = valhookSet(elem, value);
          if (elem.inputmask)
            $elem.triggerHandler('setvalue.inputmask');
          return result;
        },
        inputmaskpatch: true
      };
    }
  }

  // code omitted for brevity
}

Important line is this one:

result = valhookSet(elem, value);

It should set the new value, but it wasn't working. I've added some console logs to see what was field value after calling this function, but it didn't change. Very strange. This line is just calling valhookSet defined above:

var valhookSet = $.valHooks[type] && $.valHooks[type].set ? $.valHooks[type].set : function(elem, value) {
  elem.value = value;
  return elem;
};

This is trivial function, doesn't look like there is something wrong with it. I've copied function body inline instead of calling it and it worked. So it seems that valhookSet was something else. I've displayed it source code to console and there was this code:

// Get the element, and its data.
var $this = $(el),
  data  = $this.data('numFormat');

// Does this element have our data field?
if( !data )
{

    // Check if the valhook function already exists
    if( $.isFunction( origHookSet ) )
    {
        // There was, so go ahead and call it
        return origHookSet(el,val);
    }
    else
    {
        // No previous function, return undefined to have jQuery
        // take care of retrieving the value
        return undefined;
  }
}
else
{
  if(val == '')
  {
    return el.value = '';
  }
  // Otherwise, don't worry about other valhooks, just run ours.
  return el.value = $.number( val, data.decimals, data.dec_point, data.thousands_sep );
}

The culprit: jquery-number plugin

So it turns out this code is from jquery-number plugin. It is defining its own valhooks for reading numerical values from fields and it was conflicting with inputmask plugin.

Since we didn't use jquery-number plugin much, I've just removed it. I've only copied one function for formating numeric values.

In summary you should pay attention at what you add to your project and do you really need adding whole library if you only need couple of functions. It is always better to have less, there is less chance that there will be some problems with conflicting libraries and overall javascript will be smaller.

Visuality was started 8 years ago in some small town outside of...

Few words about front-end focused conference, that takes place in Warsaw's Praga . Or how to mix disco with emails and good UX !