Google Tag Manager Utility Function Library

A set of common helper functions for creating custom tracking scripts within GTM.

This micro-library is contained within a single variable so it can be pulled in to any other tag or variable using the underscorejs pattern of:

var _ = {{Utility Library}};

_.addEvent(document.querySelector('.article a'), 'click', function(e){
    console.log('article link click!');
});

This approach provides a few benefits:

  • No external dependencies – it doesn’t matter whether the site is using jQuery or not (it does happen!), which is a big advantage when implementing across a range of client sites.
  • The utility functions are defined once within the container and referenced everywhere else – this reduces code duplication and with it the total size of your container
  • Utility functions can be updated in one place
  • Unneeded functions can be removed if the container doesn’t require them so long as internal dependencies are met (some functions rely on one-another).
  • Written is cross-browser compatible code
  • Creates some standard patterns for solving common problems

Please note: this code has been written and iterated on over the course of several years, which means I don’t remember where it all came from. There are undoubtedly uncredited developers and libraries that I’ve lifted from, but have long since forgotten. If you see code that looks familiar, comment and tell me the source and I’m happy to attributed.

Code Structure

Library is created as a plain object, with each of the public functions added as a property. Private functions can be declared within the code but are not available in the library (for instance, helper functions for the onElementAppear. 

function(){
   "use strict";

   var _ = {};

   function privateFunction(){}

   _.publicFunction = function(){}

   return _;
}

Polyfills

As much as possible I try to write ES5 code that is backwards compatible on older browsers.

CustomEvent: create CustomEvent constructor in IE9 or higher

(function(){
  // CustomEvent Polyfill
  if ( typeof window.CustomEvent != "function" ){
    function CustomEvent ( event, params ) {
      params = params || { bubbles: false, cancelable: false, detail: undefined };
      var evt = document.createEvent( 'CustomEvent' );
      evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
      return evt;
    }
    CustomEvent.prototype = window.Event.prototype;
    window.CustomEvent = CustomEvent;
  }
})(); 

Functions

_.log

This function lets you build logging into your code, but it will only output into the console in debug mode. Debug Mode built-in variable has to be enabled for this function 

/**
 * Generic Logging Utility - only logs in debug mode
 * Accepts multiple arguments
 * @return {null}
 */
_.log = function(){
  if({{Debug Mode}}){
    console.log.apply(null, _.toArray(arguments))
  }
}

_.settingToBool

Converts a string value into a boolean, converting “true”, “yes” and “enabled” to true, all other values to false. Useful if you want to have configuration variables that are user friendly. 

/**
 * Transforms text setting into a boolean variable
 * @method settingToBool
 * @param  {string}
 * @return {bool}
 */
_.settingToBool = function(setting){
  setting = setting.toLowerCase();
  if(['true', 'yes', 'enabled'].indexOf(setting)>-1){
    return true;
  } else {
    return false;
  }
}

_.map

Similar to the map function in underscore, this utility function creates an array of values by iterating a callback over an array (or anything array-like).

/**
 * Creates an array of values by running each element in collection
 * through an iteratee. Iteratee invoked with 3 arguments - value, index, collection 
 * @param  {array}  arr - collection to iterate over
 * @param  {Function} fn - iterator function
 * @param  {context} context - context for iterator function
 * @return {array} resulting array
 */
_.map = function(arr, fn, context){
  var i, results = [];
  context = context || window;
  for(i = 0; i<arr.length; i++){
    results.push(fn.call(context, arr[i], i, arr));
  }
  return results;
}

_.listToArray

Splits a string by comma, and returns an array of each element with whitespace trimmed. Designed for use in configuration variables, for instance a “Video Percent Thresholds” variable might be “25, 50, 75, 100”. This would return [“25”, “50”, “75”, “100”].

/**
 * Split a comma separated list of arguments into a clean array
 * @method listToArray
 * @param  {string}
 * @return {array}
 */
_.listToArray = function(value){
  return _.map(value.split(','), function(item){
    return item.trim();
  });
}

_.toArray

Converts anything array-like into an array, for example the arguments keyword in a function or a NodeList from querySelectorAll.

/**
 * Converts value to array
 * @method toArray
 * @param  {Object} value - array-like object
 * @return {array}
 */
_.toArray = function(value){
  if(!value){
    return [];
  }
  return Array.prototype.slice.call(value);
}

_.extend

Combine objects left-to-right, assigning properties to a new object.

/**
 * Iterates over input objects and assigns properties to new object left-to-right
 * @param {...object} objects to be combined
 * @return {object}
 */
_.extend = function(){
  var objects = _.toArray(arguments)
  var newObject = {}
  var key
  var i
  function _extend(consumer, provider){
    var key
    for(key in provider){
      if(!consumer.hasOwnProperty(key)){
        consumer[key] = provider[key]
      }
    }
    return consumer
  }
  for(i in objects){
    objects[i] = _extend(newObject, objects[i])
  }
  return newObject
}

_.mapObject

Iterate over the keys and values within an object and return a new object with the same keys and the values the result of passing each value through a callback function.

/**
 * Create an object with the same keys as obj and values generated
 * by running each own enumerable string key property through iteratee
 * @param  {object} obj - object to iterate over
 * @param  {Function} fn - iteratee
 * @param  {object} context - context of function
 * @return {object} resulting object
 */
_.mapObject = function(obj, fn, context){
  var i, results = [];
  context = context || window;
  for(var key in obj){
    results.push(fn.call(context, obj[key], key))
  }
  return results;
}

_.timestamp

Return the current time as an unix timestamp

/**
 * Generate timestamp integer
 * @return {string}
 */
_.timestamp = function(){
  return new Date()*1;
}

_.throttle

Create a new function that can only be invoked once per every wait milliseconds. This is a very useful utility if subscribing to any browser events that fire a large number of events like scroll or mousemove. 

/**
 * Creates new throttled function that will invoke at most once per every wait
 * milliseconds
 * @param  {function} func - function to throttle
 * @param  {integer} wait - number of miliseconds to throttle invocations to
 * @return {function} returns new throttled function
 */
_.throttle = function (func, wait) {
  var context, args, result;
  var timeout = null;
  var previous = 0;
  var later = function() {
    previous = new Date;
    timeout = null;
    result = func.apply(context, args);
  };
  return function() {
    var now = new Date;
    if (!previous) previous = now;
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0) {
      clearTimeout(timeout);
      timeout = null;
      previous = now;
      result = func.apply(context, args);
    } else if (!timeout) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  };
}

_.onReady

Similar to jQuery.ready, this function calls a callback when the DOM has fully loaded

/**
 * Delay function invocation until DOM is safe to manipulate
 * @param  {Function} fn - DOM ready callback
 * @return {null}
 */
_.onReady = function (fn){
  if (document.readyState != 'loading') {
      fn();
    } else if (document.addEventListener){
      document.addEventListener('DOMContentLoaded', fn);
    } else {
      document.attachEvent('onreadystatechange', function () {
        if (document.readyState != 'loading') fn();
      });
    }
}

_.addEvent

Possibly one of the most useful utilities is this custom event handler. Easily set up callbacks on events triggered from a particular DOM element. The function returns another function that when called will remove the event listener.

/**
 * Cross-browser event handler utility
 * @param {Element} el - DOM element to attach listener to
 * @param {string}  evt - event name
 * @param {Function}  fn - callback function
 * @dependsOn removeEvent
 * @return {Function} function to call to remove event listener
 */
_.addEvent = function(el, evt, fn) { 
  if (el.addEventListener) {
    _fn = fn; 
    el.addEventListener(evt, fn);
  } else {
    _fn = function(evt) { 
      fn.call(null, el, evt); 
    };
    if (el.attachEvent) { 
      el.attachEvent('on' + evt, _fn);
    } else if (typeof el['on' + evt] === 'undefined' || el['on' + evt] === null) { 
      el['on' + evt] = _fn
    }
  }
  return function(){
    _.removeEvent(el, evt, fn);
  }
}

_.emitEvent

Trigger a custom event on a particular DOM element. Requires the aforementioned CustomEvent polyfill for Internet Explorer 9+.

/**
 * Create and emit a custom event on a particular DOM element
 * @param  {Element} target DOM element
 * @param  {String} type   event name
 * @param  {Object} opts   object to be passed to listeners
 * @return {null}        
 */
_.emitEvent = function(target, type, opts){
  var event = new CustomEvent(type, opts || {});
  target.dispatchEvent(event);
}

_.removeEvent

Remove an event listener. Requires the original function, which is why it’s usually easier to use the returned function from the addEvent function.

/**
 * Cross-browser remove event handler utilty
 * @param  {Element}   el - DOM element
 * @param  {String}   evt - event name
 * @param  {Function} fn - callback function
 * @return {null}
 */
_.removeEvent = function (el, evt, fn) { 
  if (el.removeEventListener) { 
    return el.removeEventListener(evt, fn);
  } if (el.detachEvent) { 
    return el.detachEvent('on' + evt, fn);
  } if (el['on' + evt] === fn) { 
    return el['on'] = null; 
  }
};

_.loadScript

This function calls an external JavaScript file and has a callback that is fired once that file has loaded. Helpful if you need to load a separate library, or even a JSONP call and run dependent code.

/**
 * Asyncronously load external JS library and run code once loaded
 * @param  {String}   src      URL to JavaScript file
 * @param  {Function} callback function to call when JS file is loaded
 * @return {null}            
 */
_.loadScript = function(src, callback){
  var s = document.createElement('script');
  s.type = 'text/javascript'; s.async = true; s.src = src;
  s.onload = callback;
  var x = document.getElementsByTagName('script')[0];
  x.parentNode.insertBefore(s, x);
}

_.isInView

Checks whether an element is within the view port. bounds variable adds additional padding around an element which will mean it triggers when it is slightly outside of the viewport.

/**
 * Check whether element is within the viewport
 * @param  {Element}  el     - target element
 * @param  {integer}  bounds - additional padding around element
 * @return {Boolean}         - Whether element is in viewport
 */
function _.isInView(el, bounds){
  var rect = el.getBoundingClientRect();
  return (
    (rect.bottom==rect.top==rect.left==rect.right==rect.height==rect.width==0) &amp;&amp;
    (rect.top + rect.height) >= 0 &amp;&amp;
    (rect.left + rect.width) >= 0 &amp;&amp;
    (rect.bottom - rect.height) <= ( (window.innerHeight || document.documentElement.clientHeight) + bounds) &amp;&amp;
    (rect.right - rect.width) <= ( (window.innerWidth || document.documentElement.clientWidth) + bounds)
  );
}

_.isVisible

Checks whether the. element is visible on the page (i.e. not hidden).

/**
 * Checks whether element is visible on page by recursively
 * checking visibility of parent elements
 * @param  {Element}  element target element
 * @return {Boolean}  whether element is visible or not
 */
function _.isVisible(element) {

  /**
   * Checks if a DOM element is visible. Takes into
   * consideration its parents and overflow.
   */
  function _isVisible(el, t, r, b, l, w, h) {
      var p = el.parentNode, VISIBLE_PADDING = 2

      if ( !_elementInDocument(el) ) {
          return false;
      }

      //-- Return true for document node
      if ( 9 === p.nodeType ) {
          return true;
      }

      //-- Return false if our element is invisible
      if (
           '0' === _getStyle(el, 'opacity') ||
           'none' === _getStyle(el, 'display') ||
           'hidden' === _getStyle(el, 'visibility')
      ) {
          return false;
      }
      if (
          'undefined' === typeof(t) ||
          'undefined' === typeof(r) ||
          'undefined' === typeof(b) ||
          'undefined' === typeof(l) ||
          'undefined' === typeof(w) ||
          'undefined' === typeof(h)
      ) {
          t = el.offsetTop;
          l = el.offsetLeft;
          b = t + el.offsetHeight;
          r = l + el.offsetWidth;
          w = el.offsetWidth;
          h = el.offsetHeight;
      }
      //-- If we have a parent, let's continue:
      if ( p ) {
          //-- Check if the parent can hide its children.
          if ( ('hidden' === _getStyle(p, 'overflow') || 'scroll' === _getStyle(p, 'overflow')) ) {
              //-- Only check if the offset is different for the parent
              if (
                  //-- If the target element is to the right of the parent elm
                  l + VISIBLE_PADDING > p.offsetWidth + p.scrollLeft ||
                  //-- If the target element is to the left of the parent elm
                  l + w - VISIBLE_PADDING < p.scrollLeft ||
                  //-- If the target element is under the parent elm
                  t + VISIBLE_PADDING > p.offsetHeight + p.scrollTop ||
                  //-- If the target element is above the parent elm
                  t + h - VISIBLE_PADDING < p.scrollTop
              ) {
                  //-- Our target element is out of bounds:
                  return false;
              }
          }
          //-- Add the offset parent's left/top coords to our element's offset:
          if ( el.offsetParent === p ) {
              l += p.offsetLeft;
              t += p.offsetTop;
          }
          //-- Let's recursively check upwards:
          return _isVisible(p, t, r, b, l, w, h);
      }
      return true;
  }

  //-- Cross browser method to get style properties:
  function _getStyle(el, property) {
      if ( window.getComputedStyle ) {
          return document.defaultView.getComputedStyle(el,null)[property];
      }
      if ( el.currentStyle ) {
          return el.currentStyle[property];
      }
  }

  function _elementInDocument(element) {
      while (element = element.parentNode) {
          if (element == document) {
                  return true;
          }
      }
      return false;
  }

  return _isVisible(element);

};

_.onElementAppear

Track an element on the page and trigger a callback when the element enters the viewport. Function requires two helper functions as well as being dependent on isInView and isVisible. Will also trigger if an element is hidden from view and then is displayed – including if the element is in a Bootstrap tab or accordion element. However, other types of JavaScript based show/hide functionality may require additional code so that the function knows when to check the element again.

  /**
   * Helper function - is the object a DOM node
   * @param  {Object}  o element to be tested
   * @return {Boolean}   whether object is DOM node or not
   */
  function isNode(o){
    return (
      typeof Node === "object" ? o instanceof Node : 
      o &amp;&amp; typeof o === "object" &amp;&amp; typeof o.nodeType === "number" &amp;&amp; typeof o.nodeName==="string"
    );
  }

  /**
   * Helper function - callback when element appears within viewport
   * @param  {Element}  element  element to track
   * @param  {int}      bounds   
   * @param  {Function} callback 
  
   */
  function trackElement(element, bounds,  callback){
    var fired = false;
    var change = _.throttle(function(){
      if(_.isInView(element, bounds) &amp;&amp; _.isVisible(element) &amp;&amp; !fired){
        fired = true;
        callback(element);
      }
    }, EVENT_SAMPLE_RATE);

    _.addEvent(window, "scroll", change);
    _.addEvent(window, "resize", change);
    _.addEvent(window, "click", change);

    // Bootstrap tab support
    if(window.jQuery){
      jQuery(window).on('shown.bs.tab', change);
      jQuery(window).on('shown.bs.collapse', change);
    }
    _.onReady(change);

  }

  /**
   * Fire callback function when element is visible and enters the viewport
   * @param  {Element}   element - DOM element
   * @param  {integer}   bounds  - additional margin around object
   * @param  {Function} callback - function to fire on visible
   * @dependsOn isVisible, isInView, trackElement, isNode
   * @return {null}
   */
  _.onElementAppear = function(element, bounds, callback){
    bounds = bounds || 25;
    if(isNode(element)){
      trackElement(element, bounds, callback);
    }
  }

_.cssPath

Generates a uniqueish identifier for a particular element based on the CSS classes of its parents. For example:

.off-canvas > .cta-buttons > .p2-top.p2-bottom.faq__queries__buttons..text-center > .button.link-secondary.dtm-learn-more

If any elements in the ancestry have IDs, the path will stop there. Optional arguments let you filter out classes or element types that you don’t want included, for instance the html and body elements are excluded automatically as they often have many classes that aren’t page specific.

This provides a helpful way to identify elements on the page in a universal way without always having to extract other context or information. I frequently set a custom dimension in every click-based GA hit that sets a CSS Path for the element, so that even if the click text and URL are the same, you can distinguish if needed between multiple links on the same page within the GA report.  

/**
 * Generates a CSS identifier that can be used to distinguish the element on
 * the page
 * @param  {Element} el            - target element
 * @param  {Boolean} stopOnID      - only generate path up to an element with an ID
 * @param  {array} ignoreClasses   - list of classes to not include in path
 * @param  {array} ignoreElements - list of element types not to include in path
 *                                  default are "body" and "html"
 * @dependsOn map, toArray
 * @return {String}                - css path of element, e.g. "#parent > .list > li > a"
 */
_.cssPath = function(el, stopOnID, ignoreClasses, ignoreElements) {
  stopOnID = stopOnID || false;
  ignoreElements = ignoreElements || [];
  ignoreElements = ["body", "html"].concat(ignoreElements);
  ignoreClasses = ignoreClasses || [];

  function removeA(arr, el) {
      var what, _arr = [], i, a = arguments, L = a.length, ax;
      for(i=0;i<arr.length;i++){
          if(arr[i].search(el) == -1){
              _arr.push(arr[i]);
          }
      }
      return _arr;
  }
  if (!(el instanceof Element)) return;
 
  if(el.id !== ""){
    return "#" + el.id;
  } else {
    var path = [], i = 0;
      while (el.nodeType === Node.ELEMENT_NODE) {
          var selector = el.nodeName.toLowerCase();
          var identifier = "";
          if(-1 === ignoreElements.indexOf(selector)){
              var classes = _.toArray(el.classList)
              _.map(ignoreClasses, function(cl){
                  classes = removeA(classes, cl);
              });
              if(el.id){
                  identifier = "#" + el.id;
              } else if(classes.length > 0){
                  identifier = "." + classes.join(".");
              } else if(i==0){
                  identifier = el.nodeName.toLowerCase();
              }

              if(identifier !== ""){
                  path.unshift(identifier);
              }
            
              if(stopOnID &amp;&amp; el.id){
                break;
              }
          }
          el = el.parentNode;
          i++;
      }
      return path.join(" > ");
  }    
}

_.hash

very basic hash algorithm which will generate the same random-looking string based on any input string (same input will always return the same value).

/**
 * Basic string hash utility
 * @method hash
 * @param  {string} s - input string
 * @return {string}
 */
_.hash = function(s){
  return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&amp;a},0);
}

_.parent

A helpful function for traversing up the DOM from a particular element. Will recursively walk up the DOM from the initial element until it finds an element that matches CSS selector provided in the second argument.

/**
 * Recursively walk up the DOM and create a list of elements that match
 * a defined CSS selector
 * @param  {Element} elem    - child element
 * @param  {String} selector - CSS selector of target parent element
 * @return {Array.Eleement}  - array of matching parent elements
 */
_.parent = function ( elem, selector ) {
  // Element.matches() polyfill
  if (!Element.prototype.matches) {
      Element.prototype.matches =
          Element.prototype.matchesSelector ||
          Element.prototype.mozMatchesSelector ||
          Element.prototype.msMatchesSelector ||
          Element.prototype.oMatchesSelector ||
          Element.prototype.webkitMatchesSelector ||
          function(s) {
              var matches = (this.document || this.ownerDocument).querySelectorAll(s),
                  i = matches.length;
              while (--i >= 0 &amp;&amp; matches.item(i) !== this) {}
              return i > -1;
          };
  }
  // Setup parents array
  var parents = [];
  // Get matching parent elements
  for ( ; elem &amp;&amp; elem !== document; elem = elem.parentNode ) {
      // Add matching parents to array
      if ( selector ) {
          if ( elem.matches( selector ) ) {
              parents.push( elem );
          }
      } else {
          parents.push( elem );
      }
  }
  return parents;
};

This is especially useful when you need to extract data from around a particular argument, for instance, if there is a repeated list element that you are tracking link clicks on, but you want to extract the header associated with that link click, e.g:

<div class="list-item">
  <h3>Item One</h3>
  <a href="#">Read More</a>
</div>
<div class="list-item">
  <h3>Item One</h3>
  <a href="#">Read More</a>
</div>
<div class="list-item">
  <h3>Item One</h3>
  <a href="#">Read More</a>
</div>

Say we had a trigger set up on clicks to elements matching .list-item a. We could then create a variable to extract the corresponding header element like this:

function(){
  var _ = {{Utility Library}};

  var el = {{Click Element}};

  var parent = _.parents(el, '.list-item');
  
  return parent[0].querySelector('h3').innerText;

}

_.parseURL

Breaks a URL into its component parts, including breaking the query string into a key:value object

/**
 * Parse a URL string into its component parts
 * @param  {String} url - URL string
 * @return {Object}     - key/value of URL elements including key/value of query string
 */
_.parseURL = function(url){
  a = document.createElement("a");
  a.href = url;
  query = {};
  if(a.search != ""){
    var i, vars = a.search.replace('?','').split('&amp;');
    for(i=0; i<vars.length; i++){
      var pair = vars[i].split('=');
      query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
    }

  }
  return {
    protocol: a.protocol,
    hostname: a.hostname,
    port: a.port,
    path: a.pathname,
    search: a.search,
    hash: a.hash,
    host: a.host,
    query: query
  }
}

_.titleCase

Converts a string into title case (each word in a sentence capitalized).

/**
 * Convert string to TitleCase
 * @param  {String} str input string
 * @dependsOn map
 * @return {String}     output string
 */
_.titleCase = function(str) {
  return _.map(str.split(' '), function(val){ 
    return val.charAt(0).toUpperCase() + val.substr(1).toLowerCase();
  }).join(' ');
}

_.rootDomain

Extract the root domain from a given hostname (including multiple sub-domains).

/**
 * Get the root domain of any particular hostname regardless of how
 * many sub-domains
 * @param  {String} hostname 
 * @return {String}          root domain
 */
_.rootDomain = function(hostname){
  parts = hostname.split('.');
  if(parts.length > 2){
    return [parts[parts.length-2], parts[parts.length-1]].join('.');
  } else {
    return hostname;
  }
}

_.cookie

An object with several methods to interact with cookies, including automatic JSON encoding/decoding and expiry management.

  /**
   * Utility class that provides helpful methods for working with cookies
   * @type {Object}
   */
  _.cookie = {

    /**
     * Set a cookie
     * @param {String} name    - cookie name
     * @param {Object} value   - cookie value (must be JSON-serializable)
     * @param {Integer} seconds - cookie expiration in seconds
     * @param {String} path    - optional cookie path
     * @param {String} domain  - optional cookie domain
     * @param {Boolean} secure - whether to set the cookie secure flag
     */
    set: function(name, value, seconds, path, domain, secure) {
      var date = new Date(),
          expires = '',
          type = typeof(value),
          valueToUse = '',
          secureFlag = '';
        path = path || "/";
          domain = domain || window.location.hostname;
      if (seconds) {
        date.setTime(date.getTime() + (seconds * 1000));
        expires = "; expires=" + date.toUTCString();
      }
      if (type === "object"  &amp;&amp; type !== "undefined") {
          if(!("JSON" in window)) throw "Bummer, your browser doesn't support JSON parsing.";
          valueToUse = encodeURIComponent(JSON.stringify({v:value}));
      } else {
        valueToUse = encodeURIComponent(value);
      }
      
      if (secure){
        secureFlag = "; secure";
      }

      document.cookie = name + "=" + valueToUse + expires + "; domain="+ domain +"; path=" + path + secureFlag;
    },

    /**
     * Get a cookie by name
     * @param  {String} name cookie name
     * @return {Object}      value of cookie (de-serialized if needed)
     */
    get: function(name) {
      var nameEQ = name + "=",
          ca = document.cookie.split(';'),
          value = '',
          firstChar = '',
          parsed={};
      for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) === 0) {
          value = decodeURIComponent(c.substring(nameEQ.length, c.length));
          firstChar = value.substring(0, 1);
          if(firstChar=="{"){
            try {
              parsed = JSON.parse(value);
              if("v" in parsed) return parsed.v;
            } catch(e) {
              return value;
            }
          }
          if (value=="undefined") return undefined;
          return value;
        }
      }
      return null;
    },
    /**
     * Remove cookie by name
     * @param  {String} name cookie name
     * @return {null}      
     */
    remove: function(name) {
      this.set(name, "", -1);
    },

    /**
     * Increase value of cookie by 1
     * @param  {String} name - cookie name
     * @param  {Integer} days - extend cookie life by this many days
     * @return {null}     
     */
    increment: function(name, days) {
      var value = this.get(name) || 0;
      this.set(name, (parseInt(value, 10) + 1), days*86400);
    },

    /**
     * Decrease value of cookie by 1
     * @param  {String} name - cookie name
     * @param  {Integer} days - extend cookie life by this many days
     * @return {null}      
     */
    decrement: function(name, days) {
      var value = this.get(name) || 0;
      this.set(name, (parseInt(value, 10) - 1), days*86400);
    }
  };

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.