Sunday, July 13, 2014

Javascript Input Mask jQuery Plugin

Below is a jQuery plugin that can easily be adapted to mask or limit keystrokes while you type in a textbox. This script blocks invalid keystrokes from being processed. It seems to work reliably in all modern browsers except Chrome for Android (Chrome for Android doesn't seem to fully support the keydown/keypress events). The example below only allows a valid currency value.

Credit: the caret function was borrowed from Masked Input Plugin.

Usage

$(document).ready(function()
{
 $('#text').currencyMask();
});

Code

$.fn.currencyMask = function(options)
{
    options = options || {};
    options.valid = function(s) { return s.match(/^\-?\d{0,7}(:?\.\d{0,2})?$/); };
    return this.inputMask(options);
}

$.fn.inputMask = function(options)
{
 if (this.length == 0)
        return;
 
 return this.each(function()
 {
  $(this).keydown(function(e)
  {
   if (e.which == 8 || e.which == 46) //backspace or delete?
    filter(this, false, e);
  });
  
  $(this).keypress(function(e)
  {
   if (e.which != 0 && e.which != 8) //is it printable?
    filter(this, true, e);
  });
  
  $(this).bind('paste cut blur', function(e)
  {
   setTimeout($.proxy(function()
   {
    if (!options.valid($(this).val()))
     $(this).val('');
   }, $(this)), 0);
  });
 });
 
 function filter(element, press, e)
 {
  if (!e.ctrlKey && !e.altKey && !e.metaKey)
  {
   var caret = $(element).caret();
   var begin = caret.begin;
   var end = caret.end;
   var val = $(element).val();
   var src = String.fromCharCode(e.which);
   var result = val;
   
   if (!press && e.which == 8) //backspace
   {
    begin = begin != end ? begin : Math.max(begin - 1, 0);
    result = val.substring(0, begin) + val.substring(end);
   }
   else if (!press && e.which == 46) //delete
   {
    end = begin != end ? end : Math.min(end + 1, val.length);
    result = val.substring(0, begin) + val.substring(end);
   }
   else if (press) //printable
   {
    result = val.substring(0, begin) + src + val.substring(end);
   }
   
   if (!options.valid(result))
   {
    e.preventDefault();
   }
  }
 }
};

$.fn.caret = function()
{
    if (this.length == 0)
        return;
    
 var begin = 0, end = 0;
    var item = this[0];
    
 item.focus();
    if (item.setSelectionRange)
 {
        begin = item.selectionStart;
        end = item.selectionEnd;
    }
 else if (document.selection && document.selection.createRange)
 {
        var range = document.selection.createRange();
        begin = 0 - range.duplicate().moveStart('character', -100000);
        end = begin + range.text.length;
    }
    return { begin: begin, end: end };
}

Android Fragment Replace vs. Remove+Add and the Backstack

The Android documentation has this to say about FragmentTransaction.replace:

Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.

I found the documentation to be slightly misleading because there is an important difference between replace vs. remove+add when the backstack is involved:

  1. If the backstack A->B->C is built using remove+add, then popped back to fragment A, then fragment B's onResume method will be triggered.
  2. If the backstack A->B->C is build using replace, then popped back to fragment A, then fragment B's onResume method will NOT be triggered.

Android Drawable Cache (Xamarin / Mono)

The code below is useful if you need to cache images in Android. Weak references are used to help protect against memory leaks.

Drawable Cache Class:
public class DrawableCache
{
 public Context Context { get; set; }
 Dictionary<object, WeakReference<Drawable.ConstantState>> _cache = new Dictionary<object, WeakReference<Drawable.ConstantState>>();

 public DrawableCache(Context context)
 {
  Context = context;
 }

 public void Put(object key, Drawable drawable)
 {
  _cache[key] = new WeakReference<Drawable.ConstantState>(drawable.GetConstantState(), false);
 }

 public Drawable Get(object key)
 {
  WeakReference<Drawable.ConstantState> value;
  if (_cache.TryGetValue(key, out value))
  {
   Drawable.ConstantState target;
   if (value.TryGetTarget(out target))
   {
    return target.NewDrawable(Context.Resources);
   }
   _cache.Remove(key);
  }
  return null;
 }
}
Optional Drawable Cache Key class
public class DrawableCacheKey
{
 public int Key { get; set; }
 public int Flags { get; set; }

 public DrawableCacheKey(int key, int flags)
 {
  Key = key;
  Flags = flags;
 }

 public override int GetHashCode()
 {
  int hash = 17;
  hash = hash * 23 + Key.GetHashCode();
  hash = hash * 23 + Flags.GetHashCode();
  return hash;
 }

 public override bool Equals(object obj)
 {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  var cast = obj as DrawableCacheKey;
  return cast != null && Key == cast.Key && Flags == cast.Flags;
 }
}