From d5512998d43f28959e3e751f014162371cf9d2b6 Mon Sep 17 00:00:00 2001
From: Glenn Vorhes <gavorhes@wisc.edu>
Date: Wed, 22 Jun 2016 13:44:40 -0500
Subject: [PATCH] add floathead as dependency

---
 ext/jquery.floatThead.js | 966 ---------------------------------------
 ext/sortable-table.js    | 496 --------------------
 package.json             |   2 +-
 3 files changed, 1 insertion(+), 1463 deletions(-)
 delete mode 100644 ext/jquery.floatThead.js
 delete mode 100644 ext/sortable-table.js

diff --git a/ext/jquery.floatThead.js b/ext/jquery.floatThead.js
deleted file mode 100644
index 17ed5e8..0000000
--- a/ext/jquery.floatThead.js
+++ /dev/null
@@ -1,966 +0,0 @@
-// @preserve jQuery.floatThead 1.3.2 - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2015 Misha Koryak
-// @license MIT
-
-/* @author Misha Koryak
- * @projectDescription lock a table header in place while scrolling - without breaking styles or events bound to the header
- *
- * Dependencies:
- * jquery 1.9.0 + [required] OR jquery 1.7.0 + jquery UI core
- *
- * http://mkoryak.github.io/floatThead/
- *
- * Tested on FF13+, Chrome 21+, IE8, IE9, IE10, IE11
- *
- */
-let jQuery = require('jQuery');
-
-(function( $ ) {
-  /**
-   * provides a default config object. You can modify this after including this script if you want to change the init defaults
-   * @type {Object}
-   */
-  $.floatThead = $.floatThead || {};
-  $.floatThead.defaults = {
-    headerCellSelector: 'tr:visible:first>*:visible', //thead cells are this.
-    zIndex: 1001, //zindex of the floating thead (actually a container div)
-    position: 'auto', // 'fixed', 'absolute', 'auto'. auto picks the best for your table scrolling type.
-    top: 0, //String or function($table) - offset from top of window where the header should not pass above
-    bottom: 0, //String or function($table) - offset from the bottom of the table where the header should stop scrolling
-    scrollContainer: function($table){
-      return $([]); //if the table has horizontal scroll bars then this is the container that has overflow:auto and causes those scroll bars
-    },
-    getSizingRow: function($table, $cols, $fthCells){ // this is only called when using IE,
-      // override it if the first row of the table is going to contain colgroups (any cell spans greater than one col)
-      // it should return a jquery object containing a wrapped set of table cells comprising a row that contains no col spans and is visible
-      return $table.find('tbody tr:visible:first>*:visible');
-    },
-    floatTableClass: 'floatThead-table',
-    floatWrapperClass: 'floatThead-wrapper',
-    floatContainerClass: 'floatThead-container',
-    copyTableClass: true, //copy 'class' attribute from table into the floated table so that the styles match.
-    enableAria: false, //will copy header text from the floated header back into the table for screen readers. Might cause the css styling to be off. beware!
-    autoReflow: false, //(undocumented) - use MutationObserver api to reflow automatically when internal table DOM changes
-    debug: false //print possible issues (that don't prevent script loading) to console, if console exists.
-  };
-
-  var util = window._;
-
-  var canObserveMutations = typeof MutationObserver !== 'undefined';
-
-
-  //browser stuff
-  var ieVersion = function(){for(var a=3,b=document.createElement("b"),c=b.all||[];a = 1+a,b.innerHTML="<!--[if gt IE "+ a +"]><i><![endif]-->",c[0];);return 4<a?a:document.documentMode}();
-  var isFF = /Gecko\//.test(navigator.userAgent);
-  var isWebkit = /WebKit\//.test(navigator.userAgent);
-
-  //safari 7 (and perhaps others) reports table width to be parent container's width if max-width is set on table. see: https://github.com/mkoryak/floatThead/issues/108
-  var isTableWidthBug = function(){
-    if(isWebkit) {
-      var $test = $('<div style="width:0px"><table style="max-width:100%"><tr><th><div style="min-width:100px;">X</div></th></tr></table></div>');
-      $("body").append($test);
-      var ret = ($test.find("table").width() == 0);
-      $test.remove();
-      return ret;
-    }
-    return false;
-  };
-
-  var createElements = !isFF && !ieVersion; //FF can read width from <col> elements, but webkit cannot
-
-  var $window = $(window);
-
-  /**
-   * @param debounceMs
-   * @param cb
-   */
-  function windowResize(eventName, cb){
-    if(ieVersion == 8){ //ie8 is crap: https://github.com/mkoryak/floatThead/issues/65
-      var winWidth = $window.width();
-      var debouncedCb = util.debounce(function(){
-        var winWidthNew = $window.width();
-        if(winWidth != winWidthNew){
-          winWidth = winWidthNew;
-          cb();
-        }
-      }, 1);
-      $window.on(eventName, debouncedCb);
-    } else {
-      $window.on(eventName, util.debounce(cb, 1));
-    }
-  }
-
-
-  function debug(str){
-    window && window.console && window.console.error && window.console.error("jQuery.floatThead: " + str);
-  }
-
-  //returns fractional pixel widths
-  function getOffsetWidth(el) {
-    var rect = el.getBoundingClientRect();
-    return rect.width || rect.right - rect.left;
-  }
-
-  /**
-   * try to calculate the scrollbar width for your browser/os
-   * @return {Number}
-   */
-  function scrollbarWidth() {
-    var $div = $( //borrowed from anti-scroll
-        '<div style="width:50px;height:50px;overflow-y:scroll;'
-        + 'position:absolute;top:-200px;left:-200px;"><div style="height:100px;width:100%">'
-        + '</div>'
-    );
-    $('body').append($div);
-    var w1 = $div.innerWidth();
-    var w2 = $('div', $div).innerWidth();
-    $div.remove();
-    return w1 - w2;
-  }
-  /**
-   * Check if a given table has been datatableized (http://datatables.net)
-   * @param $table
-   * @return {Boolean}
-   */
-  function isDatatable($table){
-    if($table.dataTableSettings){
-      for(var i = 0; i < $table.dataTableSettings.length; i++){
-        var table = $table.dataTableSettings[i].nTable;
-        if($table[0] == table){
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  function tableWidth($table, $fthCells, isOuter){
-    // see: https://github.com/mkoryak/floatThead/issues/108
-    var fn = isOuter ? "outerWidth": "width";
-    if(isTableWidthBug && $table.css("max-width")){
-      var w = 0;
-      if(isOuter) {
-        w += parseInt($table.css("borderLeft"), 10);
-        w += parseInt($table.css("borderRight"), 10);
-      }
-      for(var i=0; i < $fthCells.length; i++){
-        w += $fthCells.get(i).offsetWidth;
-      }
-      return w;
-    } else {
-      return $table[fn]();
-    }
-  }
-  $.fn.floatThead = function(map){
-    map = map || {};
-    if(!util){ //may have been included after the script? lets try to grab it again.
-      util = window._ || $.floatThead._;
-      if(!util){
-        throw new Error("jquery.floatThead-slim.js requires underscore. You should use the non-lite version since you do not have underscore.");
-      }
-    }
-
-    if(ieVersion < 8){
-      return this; //no more crappy browser support.
-    }
-
-    var mObs = null; //mutation observer lives in here if we can use it / make it
-
-    if(util.isFunction(isTableWidthBug)) {
-      isTableWidthBug = isTableWidthBug();
-    }
-
-    if(util.isString(map)){
-      var command = map;
-      var ret = this;
-      this.filter('table').each(function(){
-        var $this = $(this);
-        var opts = $this.data('floatThead-lazy');
-        if(opts){
-          $this.floatThead(opts);
-        }
-        var obj = $this.data('floatThead-attached');
-        if(obj && util.isFunction(obj[command])){
-          var r = obj[command]();
-          if(typeof r !== 'undefined'){
-            ret = r;
-          }
-        }
-      });
-      return ret;
-    }
-    var opts = $.extend({}, $.floatThead.defaults || {}, map);
-
-    $.each(map, function(key, val){
-      if((!(key in $.floatThead.defaults)) && opts.debug){
-        debug("Used ["+key+"] key to init plugin, but that param is not an option for the plugin. Valid options are: "+ (util.keys($.floatThead.defaults)).join(', '));
-      }
-    });
-    if(opts.debug){
-      var v = $.fn.jquery.split(".");
-      if(parseInt(v[0], 10) == 1 && parseInt(v[1], 10) <= 7){
-        debug("jQuery version "+$.fn.jquery+" detected! This plugin supports 1.8 or better, or 1.7.x with jQuery UI 1.8.24 -> http://jqueryui.com/resources/download/jquery-ui-1.8.24.zip")
-      }
-    }
-
-    this.filter(':not(.'+opts.floatTableClass+')').each(function(){
-      var floatTheadId = util.uniqueId();
-      var $table = $(this);
-      if($table.data('floatThead-attached')){
-        return true; //continue the each loop
-      }
-      if(!$table.is('table')){
-        throw new Error('jQuery.floatThead must be run on a table element. ex: $("table").floatThead();');
-      }
-      canObserveMutations = opts.autoReflow && canObserveMutations; //option defaults to false!
-      var $header = $table.children('thead:first');
-      var $tbody = $table.children('tbody:first');
-      if($header.length == 0 || $tbody.length == 0){
-        $table.data('floatThead-lazy', opts);
-        $table.unbind("reflow").one('reflow', function(){
-          $table.floatThead(opts);
-        });
-        return;
-      }
-      if($table.data('floatThead-lazy')){
-        $table.unbind("reflow");
-      }
-      $table.data('floatThead-lazy', false);
-
-      var headerFloated = true;
-      var scrollingTop, scrollingBottom;
-      var scrollbarOffset = {vertical: 0, horizontal: 0};
-      var scWidth = scrollbarWidth();
-      var lastColumnCount = 0; //used by columnNum()
-      var $scrollContainer = opts.scrollContainer($table) || $([]); //guard against returned nulls
-      var locked = $scrollContainer.length > 0;
-
-      var useAbsolutePositioning = null;
-      if(typeof opts.useAbsolutePositioning !== 'undefined'){
-        opts.position = 'auto';
-        if(opts.useAbsolutePositioning){
-          opts.position = opts.useAbsolutePositioning ? 'absolute' : 'fixed';
-        }
-        debug("option 'useAbsolutePositioning' has been removed in v1.3.0, use `position:'"+opts.position+"'` instead. See docs for more info: http://mkoryak.github.io/floatThead/#options")
-      }
-      if(typeof opts.scrollingTop !== 'undefined'){
-        opts.top = opts.scrollingTop;
-        debug("option 'scrollingTop' has been renamed to 'top' in v1.3.0. See docs for more info: http://mkoryak.github.io/floatThead/#options");
-      }
-      if(typeof opts.scrollingBottom !== 'undefined'){
-          opts.bottom = opts.scrollingBottom;
-          debug("option 'scrollingBottom' has been renamed to 'bottom' in v1.3.0. See docs for more info: http://mkoryak.github.io/floatThead/#options");
-      }
-
-
-      if (opts.position == 'auto') {
-        useAbsolutePositioning = null;
-      } else if (opts.position == 'fixed') {
-        useAbsolutePositioning = false;
-      } else if (opts.position == 'absolute'){
-        useAbsolutePositioning = true;
-      } else if (opts.debug) {
-        debug('Invalid value given to "position" option, valid is "fixed", "absolute" and "auto". You passed: ', opts.position);
-      }
-
-      if(useAbsolutePositioning == null){ //defaults: locked=true, !locked=false
-        useAbsolutePositioning = locked;
-      }
-      var $caption = $table.find("caption");
-      var haveCaption = $caption.length == 1;
-      if(haveCaption){
-        var captionAlignTop = ($caption.css("caption-side") || $caption.attr("align") || "top") === "top";
-      }
-
-      var $fthGrp = $('<fthfoot style="display:table-footer-group;border-spacing:0;height:0;border-collapse:collapse;visibility:hidden"/>');
-
-      var wrappedContainer = false; //used with absolute positioning enabled. did we need to wrap the scrollContainer/table with a relative div?
-      var $wrapper = $([]); //used when absolute positioning enabled - wraps the table and the float container
-      var absoluteToFixedOnScroll = ieVersion <= 9 && !locked && useAbsolutePositioning; //on IE using absolute positioning doesn't look good with window scrolling, so we change position to fixed on scroll, and then change it back to absolute when done.
-      var $floatTable = $("<table/>");
-      var $floatColGroup = $("<colgroup/>");
-      var $tableColGroup = $table.children('colgroup:first');
-      var existingColGroup = true;
-      if($tableColGroup.length == 0){
-        $tableColGroup = $("<colgroup/>");
-        existingColGroup = false;
-      }
-      var $fthRow = $('<fthtr style="display:table-row;border-spacing:0;height:0;border-collapse:collapse"/>'); //created unstyled elements (used for sizing the table because chrome can't read <col> width)
-      var $floatContainer = $('<div style="overflow: hidden;" aria-hidden="true"></div>');
-      var floatTableHidden = false; //this happens when the table is hidden and we do magic when making it visible
-      var $newHeader = $("<thead/>");
-      var $sizerRow = $('<tr class="size-row"/>');
-      var $sizerCells = $([]);
-      var $tableCells = $([]); //used for sizing - either $sizerCells or $tableColGroup cols. $tableColGroup cols are only created in chrome for borderCollapse:collapse because of a chrome bug.
-      var $headerCells = $([]);
-      var $fthCells = $([]); //created elements
-
-      $newHeader.append($sizerRow);
-      $table.prepend($tableColGroup);
-      if(createElements){
-        $fthGrp.append($fthRow);
-        $table.append($fthGrp);
-      }
-
-      $floatTable.append($floatColGroup);
-      $floatContainer.append($floatTable);
-      if(opts.copyTableClass){
-        $floatTable.attr('class', $table.attr('class'));
-      }
-      $floatTable.attr({ //copy over some deprecated table attributes that people still like to use. Good thing people don't use colgroups...
-        'cellpadding': $table.attr('cellpadding'),
-        'cellspacing': $table.attr('cellspacing'),
-        'border': $table.attr('border')
-      });
-      var tableDisplayCss = $table.css('display');
-      $floatTable.css({
-        'borderCollapse': $table.css('borderCollapse'),
-        'border': $table.css('border'),
-        'display': tableDisplayCss
-      });
-      if(tableDisplayCss == 'none'){
-        floatTableHidden = true;
-      }
-
-      $floatTable.addClass(opts.floatTableClass).css({'margin': 0, 'border-bottom-width': 0}); //must have no margins or you won't be able to click on things under floating table
-
-      if(useAbsolutePositioning){
-        var makeRelative = function($container, alwaysWrap){
-          var positionCss = $container.css('position');
-          var relativeToScrollContainer = (positionCss == "relative" || positionCss == "absolute");
-          var $containerWrap = $container;
-          if(!relativeToScrollContainer || alwaysWrap){
-            var css = {"paddingLeft": $container.css('paddingLeft'), "paddingRight": $container.css('paddingRight')};
-            $floatContainer.css(css);
-            $containerWrap = $container.data('floatThead-containerWrap') || $container.wrap("<div class='"+opts.floatWrapperClass+"' style='position: relative; clear:both;'></div>").parent();
-            $container.data('floatThead-containerWrap', $containerWrap); //multiple tables inside one scrolling container - #242
-            wrappedContainer = true;
-          }
-          return $containerWrap;
-        };
-        if(locked){
-          $wrapper = makeRelative($scrollContainer, true);
-          $wrapper.prepend($floatContainer);
-        } else {
-          $wrapper = makeRelative($table);
-          $table.before($floatContainer);
-        }
-      } else {
-        $table.before($floatContainer);
-      }
-
-
-      $floatContainer.css({
-        position: useAbsolutePositioning ? 'absolute' : 'fixed',
-        marginTop: 0,
-        top:  useAbsolutePositioning ? 0 : 'auto',
-        zIndex: opts.zIndex
-      });
-      $floatContainer.addClass(opts.floatContainerClass);
-      updateScrollingOffsets();
-
-      var layoutFixed = {'table-layout': 'fixed'};
-      var layoutAuto = {'table-layout': $table.css('tableLayout') || 'auto'};
-      var originalTableWidth = $table[0].style.width || ""; //setting this to auto is bad: #70
-      var originalTableMinWidth = $table.css('minWidth') || "";
-
-      function eventName(name){
-        return name+'.fth-'+floatTheadId+'.floatTHead'
-      }
-
-      function setHeaderHeight(){
-        var headerHeight = 0;
-        $header.children("tr:visible").each(function(){
-          headerHeight += $(this).outerHeight(true);
-        });
-        if($table.css('border-collapse') == 'collapse') {
-          var tableBorderTopHeight = parseInt($table.css('border-top-width'), 10);
-          var cellBorderTopHeight = parseInt($table.find("thead tr:first").find(">*:first").css('border-top-width'), 10);
-          if(tableBorderTopHeight > cellBorderTopHeight) {
-            headerHeight -= (tableBorderTopHeight / 2); //id love to see some docs where this magic recipe is found..
-          }
-        }
-        $sizerRow.outerHeight(headerHeight);
-        $sizerCells.outerHeight(headerHeight);
-      }
-
-
-      function setFloatWidth(){
-        var tw = tableWidth($table, $fthCells, true);
-        var width = $scrollContainer.width() || tw;
-        var floatContainerWidth = $scrollContainer.css("overflow-y") != 'hidden' ? width - scrollbarOffset.vertical : width;
-        $floatContainer.width(floatContainerWidth);
-        if(locked){
-          var percent = 100 * tw / (floatContainerWidth);
-          $floatTable.css('width', percent+'%');
-        } else {
-          $floatTable.outerWidth(tw);
-        }
-      }
-
-      function updateScrollingOffsets(){
-        scrollingTop = (util.isFunction(opts.top) ? opts.top($table) : opts.top) || 0;
-        scrollingBottom = (util.isFunction(opts.bottom) ? opts.bottom($table) : opts.bottom) || 0;
-      }
-
-      /**
-       * get the number of columns and also rebuild resizer rows if the count is different than the last count
-       */
-      function columnNum(){
-        var count;
-        var $headerColumns = $header.find(opts.headerCellSelector);
-        if(existingColGroup){
-          count = $tableColGroup.find('col').length;
-        } else {
-          count = 0;
-          $headerColumns.each(function () {
-              count += parseInt(($(this).attr('colspan') || 1), 10);
-          });
-        }
-        if(count != lastColumnCount){
-          lastColumnCount = count;
-          var cells = [], cols = [], psuedo = [], content;
-          for(var x = 0; x < count; x++){
-            if (opts.enableAria && (content = $headerColumns.eq(x).text()) ) {
-              cells.push('<th scope="col" class="floatThead-col">' + content + '</th>');
-            } else {
-              cells.push('<th class="floatThead-col"/>');
-            }
-            cols.push('<col/>');
-            psuedo.push("<fthtd style='display:table-cell;height:0;width:auto;'/>");
-          }
-
-          cols = cols.join('');
-          cells = cells.join('');
-
-          if(createElements){
-            psuedo = psuedo.join('');
-            $fthRow.html(psuedo);
-            $fthCells = $fthRow.find('fthtd');
-          }
-
-          $sizerRow.html(cells);
-          $sizerCells = $sizerRow.find("th");
-          if(!existingColGroup){
-            $tableColGroup.html(cols);
-          }
-          $tableCells = $tableColGroup.find('col');
-          $floatColGroup.html(cols);
-          $headerCells = $floatColGroup.find("col");
-
-        }
-        return count;
-      }
-
-      function refloat(){ //make the thing float
-        if(!headerFloated){
-          headerFloated = true;
-          if(useAbsolutePositioning){ //#53, #56
-            var tw = tableWidth($table, $fthCells, true);
-            var wrapperWidth = $wrapper.width();
-            if(tw > wrapperWidth){
-              $table.css('minWidth', tw);
-            }
-          }
-          $table.css(layoutFixed);
-          $floatTable.css(layoutFixed);
-          $floatTable.append($header); //append because colgroup must go first in chrome
-          $tbody.before($newHeader);
-          setHeaderHeight();
-        }
-      }
-      function unfloat(){ //put the header back into the table
-        if(headerFloated){
-          headerFloated = false;
-          if(useAbsolutePositioning){ //#53, #56
-            $table.width(originalTableWidth);
-          }
-          $newHeader.detach();
-          $table.prepend($header);
-          $table.css(layoutAuto);
-          $floatTable.css(layoutAuto);
-          $table.css('minWidth', originalTableMinWidth); //this looks weird, but it's not a bug. Think about it!!
-          $table.css('minWidth', tableWidth($table, $fthCells)); //#121
-        }
-      }
-      var isHeaderFloatingLogical = false; //for the purpose of this event, the header is/isnt floating, even though the element
-                                           //might be in some other state. this is what the header looks like to the user
-      function triggerFloatEvent(isFloating){
-        if(isHeaderFloatingLogical != isFloating){
-          isHeaderFloatingLogical = isFloating;
-          $table.triggerHandler("floatThead", [isFloating, $floatContainer])
-        }
-      }
-      function changePositioning(isAbsolute){
-        if(useAbsolutePositioning != isAbsolute){
-          useAbsolutePositioning = isAbsolute;
-          $floatContainer.css({
-            position: useAbsolutePositioning ? 'absolute' : 'fixed'
-          });
-        }
-      }
-      function getSizingRow($table, $cols, $fthCells, ieVersion){
-        if(createElements){
-          return $fthCells;
-        } else if(ieVersion) {
-          return opts.getSizingRow($table, $cols, $fthCells);
-        } else {
-          return $cols;
-        }
-      }
-
-      /**
-       * returns a function that updates the floating header's cell widths.
-       * @return {Function}
-       */
-      function reflow(){
-        var i;
-        var numCols = columnNum(); //if the tables columns changed dynamically since last time (datatables), rebuild the sizer rows and get a new count
-
-        return function(){
-          $tableCells = $tableColGroup.find('col');
-          var $rowCells = getSizingRow($table, $tableCells, $fthCells, ieVersion);
-
-          if($rowCells.length == numCols && numCols > 0){
-            if(!existingColGroup){
-              for(i=0; i < numCols; i++){
-                $tableCells.eq(i).css('width', '');
-              }
-            }
-            unfloat();
-            var widths = [];
-            for(i=0; i < numCols; i++){
-              widths[i] = getOffsetWidth($rowCells.get(i));
-            }
-            for(i=0; i < numCols; i++){
-              $headerCells.eq(i).width(widths[i]);
-              $tableCells.eq(i).width(widths[i]);
-            }
-            refloat();
-          } else {
-            $floatTable.append($header);
-            $table.css(layoutAuto);
-            $floatTable.css(layoutAuto);
-            setHeaderHeight();
-          }
-          $table.triggerHandler("reflowed", [$floatContainer]);
-        };
-      }
-
-      function floatContainerBorderWidth(side){
-        var border = $scrollContainer.css("border-"+side+"-width");
-        var w = 0;
-        if (border && ~border.indexOf('px')) {
-          w = parseInt(border, 10);
-        }
-        return w;
-      }
-      /**
-       * first performs initial calculations that we expect to not change when the table, window, or scrolling container are scrolled.
-       * returns a function that calculates the floating container's top and left coords. takes into account if we are using page scrolling or inner scrolling
-       * @return {Function}
-       */
-      function calculateFloatContainerPosFn(){
-        var scrollingContainerTop = $scrollContainer.scrollTop();
-
-        //this floatEnd calc was moved out of the returned function because we assume the table height doesn't change (otherwise we must reinit by calling calculateFloatContainerPosFn)
-        var floatEnd;
-        var tableContainerGap = 0;
-        var captionHeight = haveCaption ? $caption.outerHeight(true) : 0;
-        var captionScrollOffset = captionAlignTop ? captionHeight : -captionHeight;
-
-        var floatContainerHeight = $floatContainer.height();
-        var tableOffset = $table.offset();
-        var tableLeftGap = 0; //can be caused by border on container (only in locked mode)
-        var tableTopGap = 0;
-        if(locked){
-          var containerOffset = $scrollContainer.offset();
-          tableContainerGap = tableOffset.top - containerOffset.top + scrollingContainerTop;
-          if(haveCaption && captionAlignTop){
-            tableContainerGap += captionHeight;
-          }
-          tableLeftGap = floatContainerBorderWidth('left');
-          tableTopGap = floatContainerBorderWidth('top');
-          tableContainerGap -= tableTopGap;
-        } else {
-          floatEnd = tableOffset.top - scrollingTop - floatContainerHeight + scrollingBottom + scrollbarOffset.horizontal;
-        }
-        var windowTop = $window.scrollTop();
-        var windowLeft = $window.scrollLeft();
-        var scrollContainerLeft =  $scrollContainer.scrollLeft();
-
-        return function(eventType){
-          var isTableHidden = $table[0].offsetWidth <= 0 && $table[0].offsetHeight <= 0;
-          if(!isTableHidden && floatTableHidden) {
-            floatTableHidden = false;
-            setTimeout(function(){
-              $table.triggerHandler("reflow");
-            }, 1);
-            return null;
-          }
-          if(isTableHidden){ //it's hidden
-            floatTableHidden = true;
-            if(!useAbsolutePositioning){
-              return null;
-            }
-          }
-
-          if(eventType == 'windowScroll'){
-            windowTop = $window.scrollTop();
-            windowLeft = $window.scrollLeft();
-          } else if(eventType == 'containerScroll'){
-            scrollingContainerTop = $scrollContainer.scrollTop();
-            scrollContainerLeft =  $scrollContainer.scrollLeft();
-          } else if(eventType != 'init') {
-            windowTop = $window.scrollTop();
-            windowLeft = $window.scrollLeft();
-            scrollingContainerTop = $scrollContainer.scrollTop();
-            scrollContainerLeft =  $scrollContainer.scrollLeft();
-          }
-          if(isWebkit && (windowTop < 0 || windowLeft < 0)){ //chrome overscroll effect at the top of the page - breaks fixed positioned floated headers
-            return;
-          }
-
-          if(absoluteToFixedOnScroll){
-            if(eventType == 'windowScrollDone'){
-              changePositioning(true); //change to absolute
-            } else {
-              changePositioning(false); //change to fixed
-            }
-          } else if(eventType == 'windowScrollDone'){
-            return null; //event is fired when they stop scrolling. ignore it if not 'absoluteToFixedOnScroll'
-          }
-
-          tableOffset = $table.offset();
-          if(haveCaption && captionAlignTop){
-            tableOffset.top += captionHeight;
-          }
-          var top, left;
-          var tableHeight = $table.outerHeight();
-
-          if(locked && useAbsolutePositioning){ //inner scrolling, absolute positioning
-            if (tableContainerGap >= scrollingContainerTop) {
-              var gap = tableContainerGap - scrollingContainerTop + tableTopGap;
-              top = gap > 0 ? gap : 0;
-              triggerFloatEvent(false);
-            } else {
-              top = wrappedContainer ? tableTopGap : scrollingContainerTop;
-              //headers stop at the top of the viewport
-              triggerFloatEvent(true);
-            }
-            left = tableLeftGap;
-          } else if(!locked && useAbsolutePositioning) { //window scrolling, absolute positioning
-            if(windowTop > floatEnd + tableHeight + captionScrollOffset){
-              top = tableHeight - floatContainerHeight + captionScrollOffset; //scrolled past table
-            } else if (tableOffset.top >= windowTop + scrollingTop) {
-              top = 0; //scrolling to table
-              unfloat();
-              triggerFloatEvent(false);
-            } else {
-              top = scrollingTop + windowTop - tableOffset.top + tableContainerGap + (captionAlignTop ? captionHeight : 0);
-              refloat(); //scrolling within table. header floated
-              triggerFloatEvent(true);
-            }
-            left =  0;
-          } else if(locked && !useAbsolutePositioning){ //inner scrolling, fixed positioning
-            if (tableContainerGap > scrollingContainerTop || scrollingContainerTop - tableContainerGap > tableHeight) {
-              top = tableOffset.top - windowTop;
-              unfloat();
-              triggerFloatEvent(false);
-            } else {
-              top = tableOffset.top + scrollingContainerTop  - windowTop - tableContainerGap;
-              refloat();
-              triggerFloatEvent(true);
-              //headers stop at the top of the viewport
-            }
-            left = tableOffset.left + scrollContainerLeft - windowLeft;
-          } else if(!locked && !useAbsolutePositioning) { //window scrolling, fixed positioning
-            if(windowTop > floatEnd + tableHeight + captionScrollOffset){
-              top = tableHeight + scrollingTop - windowTop + floatEnd + captionScrollOffset;
-              //scrolled past the bottom of the table
-            } else if (tableOffset.top > windowTop + scrollingTop) {
-              top = tableOffset.top - windowTop;
-              refloat();
-              triggerFloatEvent(false); //this is a weird case, the header never gets unfloated and i have no no way to know
-              //scrolled past the top of the table
-            } else {
-              //scrolling within the table
-              top = scrollingTop;
-              triggerFloatEvent(true);
-            }
-            left = tableOffset.left - windowLeft;
-          }
-          return {top: top, left: left};
-        };
-      }
-      /**
-       * returns a function that caches old floating container position and only updates css when the position changes
-       * @return {Function}
-       */
-      function repositionFloatContainerFn(){
-        var oldTop = null;
-        var oldLeft = null;
-        var oldScrollLeft = null;
-        return function(pos, setWidth, setHeight){
-          if(pos != null && (oldTop != pos.top || oldLeft != pos.left)){
-            $floatContainer.css({
-              top: pos.top,
-              left: pos.left
-            });
-            oldTop = pos.top;
-            oldLeft = pos.left;
-          }
-          if(setWidth){
-            setFloatWidth();
-          }
-          if(setHeight){
-            setHeaderHeight();
-          }
-          var scrollLeft = $scrollContainer.scrollLeft();
-          if(!useAbsolutePositioning || oldScrollLeft != scrollLeft){
-            $floatContainer.scrollLeft(scrollLeft);
-            oldScrollLeft = scrollLeft;
-          }
-        }
-      }
-
-      /**
-       * checks if THIS table has scrollbars, and finds their widths
-       */
-      function calculateScrollBarSize(){ //this should happen after the floating table has been positioned
-        if($scrollContainer.length){
-          if($scrollContainer.data().perfectScrollbar){
-            scrollbarOffset = {horizontal:0, vertical:0};
-          } else {
-            var sw = $scrollContainer.width(), sh = $scrollContainer.height(), th = $table.height(), tw = tableWidth($table, $fthCells);
-            var offseth = sw < tw ? scWidth : 0;
-            var offsetv = sh < th ? scWidth : 0;
-            scrollbarOffset.horizontal = sw - offsetv < tw ? scWidth : 0;
-            scrollbarOffset.vertical = sh - offseth < th ? scWidth : 0;
-          }
-        }
-      }
-      //finish up. create all calculation functions and bind them to events
-      calculateScrollBarSize();
-
-      var flow;
-
-      var ensureReflow = function(){
-        flow = reflow();
-        flow();
-      };
-
-      ensureReflow();
-
-      var calculateFloatContainerPos = calculateFloatContainerPosFn();
-      var repositionFloatContainer = repositionFloatContainerFn();
-
-      repositionFloatContainer(calculateFloatContainerPos('init'), true); //this must come after reflow because reflow changes scrollLeft back to 0 when it rips out the thead
-
-      var windowScrollDoneEvent = util.debounce(function(){
-        repositionFloatContainer(calculateFloatContainerPos('windowScrollDone'), false);
-      }, 1);
-
-      var windowScrollEvent = function(){
-        repositionFloatContainer(calculateFloatContainerPos('windowScroll'), false);
-        if(absoluteToFixedOnScroll){
-          windowScrollDoneEvent();
-        }
-      };
-      var containerScrollEvent = function(){
-        repositionFloatContainer(calculateFloatContainerPos('containerScroll'), false);
-      };
-
-
-      var windowResizeEvent = function(){
-        if($table.is(":hidden")){
-          return;
-        }
-        updateScrollingOffsets();
-        calculateScrollBarSize();
-        ensureReflow();
-        calculateFloatContainerPos = calculateFloatContainerPosFn();
-        repositionFloatContainer = repositionFloatContainerFn();
-        repositionFloatContainer(calculateFloatContainerPos('resize'), true, true);
-      };
-      var reflowEvent = util.debounce(function(){
-        if($table.is(":hidden")){
-          return;
-        }
-        calculateScrollBarSize();
-        updateScrollingOffsets();
-        ensureReflow();
-        calculateFloatContainerPos = calculateFloatContainerPosFn();
-        repositionFloatContainer(calculateFloatContainerPos('reflow'), true);
-      }, 1);
-      if(locked){ //internal scrolling
-        if(useAbsolutePositioning){
-          $scrollContainer.on(eventName('scroll'), containerScrollEvent);
-        } else {
-          $scrollContainer.on(eventName('scroll'), containerScrollEvent);
-          $window.on(eventName('scroll'), windowScrollEvent);
-        }
-      } else { //window scrolling
-        $window.on(eventName('scroll'), windowScrollEvent);
-      }
-
-      $window.on(eventName('load'), reflowEvent); //for tables with images
-
-      windowResize(eventName('resize'), windowResizeEvent);
-      $table.on('reflow', reflowEvent);
-      if(isDatatable($table)){
-        $table
-          .on('filter', reflowEvent)
-          .on('sort',   reflowEvent)
-          .on('page',   reflowEvent);
-      }
-
-      $window.on(eventName('shown.bs.tab'), reflowEvent); // people cant seem to figure out how to use this plugin with bs3 tabs... so this :P
-      $window.on(eventName('tabsactivate'), reflowEvent); // same thing for jqueryui
-
-
-      if (canObserveMutations) {
-        var mutationElement = null;
-        if(util.isFunction(opts.autoReflow)){
-          mutationElement = opts.autoReflow($table, $scrollContainer)
-        }
-        if(!mutationElement) {
-          mutationElement = $scrollContainer.length ? $scrollContainer[0] : $table[0]
-        }
-        mObs = new MutationObserver(function(e){
-          var wasTableRelated = function(nodes){
-            return nodes && nodes[0] && (nodes[0].nodeName == "THEAD" || nodes[0].nodeName == "TD"|| nodes[0].nodeName == "TH");
-          };
-          for(var i=0; i < e.length; i++){
-            if(!(wasTableRelated(e[i].addedNodes) || wasTableRelated(e[i].removedNodes))){
-              reflowEvent();
-              break;
-            }
-          }
-        });
-        mObs.observe(mutationElement, {
-            childList: true,
-            subtree: true
-        });
-      }
-
-      //attach some useful functions to the table.
-      $table.data('floatThead-attached', {
-        destroy: function(){
-          var ns = '.fth-'+floatTheadId;
-          unfloat();
-          $table.css(layoutAuto);
-          $tableColGroup.remove();
-          createElements && $fthGrp.remove();
-          if($newHeader.parent().length){ //only if it's in the DOM
-            $newHeader.replaceWith($header);
-          }
-          if(canObserveMutations){
-            mObs.disconnect();
-            mObs = null;
-          }
-          $table.off('reflow reflowed');
-          $scrollContainer.off(ns);
-          if (wrappedContainer) {
-            if ($scrollContainer.length) {
-              $scrollContainer.unwrap();
-            }
-            else {
-              $table.unwrap();
-            }
-          }
-          if(locked){
-            $scrollContainer.data('floatThead-containerWrap', false);
-          } else {
-            $table.data('floatThead-containerWrap', false);
-          }
-          $table.css('minWidth', originalTableMinWidth);
-          $floatContainer.remove();
-          $table.data('floatThead-attached', false);
-          $window.off(ns);
-        },
-        reflow: function(){
-          reflowEvent();
-        },
-        setHeaderHeight: function(){
-          setHeaderHeight();
-        },
-        getFloatContainer: function(){
-          return $floatContainer;
-        },
-        getRowGroups: function(){
-          if(headerFloated){
-            return $floatContainer.find('>table>thead').add($table.children("tbody,tfoot"));
-          } else {
-            return $table.children("thead,tbody,tfoot");
-          }
-        }
-      });
-    });
-    return this;
-  };
-})(jQuery);
-
-/* jQuery.floatThead.utils - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2014 Misha Koryak
- * License: MIT
- *
- * This file is required if you do not use underscore in your project and you want to use floatThead.
- * It contains functions from underscore that the plugin uses.
- *
- * YOU DON'T NEED TO INCLUDE THIS IF YOU ALREADY INCLUDE UNDERSCORE!
- *
- */
-
-(function($){
-
-  $.floatThead = $.floatThead || {};
-
-  $.floatThead._  = window._ || (function(){
-    var that = {};
-    var hasOwnProperty = Object.prototype.hasOwnProperty, isThings = ['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'];
-    that.has = function(obj, key) {
-      return hasOwnProperty.call(obj, key);
-    };
-    that.keys = function(obj) {
-      if (obj !== Object(obj)) throw new TypeError('Invalid object');
-      var keys = [];
-      for (var key in obj) if (that.has(obj, key)) keys.push(key);
-      return keys;
-    };
-    var idCounter = 0;
-    that.uniqueId = function(prefix) {
-      var id = ++idCounter + '';
-      return prefix ? prefix + id : id;
-    };
-    $.each(isThings, function(){
-      var name = this;
-      that['is' + name] = function(obj) {
-        return Object.prototype.toString.call(obj) == '[object ' + name + ']';
-      };
-    });
-    that.debounce = function(func, wait, immediate) {
-      var timeout, args, context, timestamp, result;
-      return function() {
-        context = this;
-        args = arguments;
-        timestamp = new Date();
-        var later = function() {
-          var last = (new Date()) - timestamp;
-          if (last < wait) {
-            timeout = setTimeout(later, wait - last);
-          } else {
-            timeout = null;
-            if (!immediate) result = func.apply(context, args);
-          }
-        };
-        var callNow = immediate && !timeout;
-        if (!timeout) {
-          timeout = setTimeout(later, wait);
-        }
-        if (callNow) result = func.apply(context, args);
-        return result;
-      };
-    };
-    return that;
-  })();
-})(jQuery);
-
diff --git a/ext/sortable-table.js b/ext/sortable-table.js
deleted file mode 100644
index 5e91d8b..0000000
--- a/ext/sortable-table.js
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
-  SortTable
-  version 2
-  7th April 2007
-  Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
-
-  Instructions:
-  Download this file
-  Add <script src="sorttable.js"></script> to your HTML
-  Add class="sortable" to any table you'd like to make sortable
-  Click on the headers to sort
-
-  Thanks to many, many people for contributions and suggestions.
-  Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
-  This basically means: do what you want with it.
-*/
-
-
-var stIsIE = /*@cc_on!@*/false;
-
-var sorttable = {
-  init: function() {
-    // quit if this function has already been called
-    if (arguments.callee.done) return;
-    // flag this function so we don't do the same thing twice
-    arguments.callee.done = true;
-    // kill the timer
-    if (_timer) clearInterval(_timer);
-
-    if (!document.createElement || !document.getElementsByTagName) return;
-
-    sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
-
-    forEach(document.getElementsByTagName('table'), function(table) {
-      if (table.className.search(/\bsortable\b/) != -1) {
-        sorttable.makeSortable(table);
-      }
-    });
-
-  },
-
-  makeSortable: function(table) {
-    if (table.getElementsByTagName('thead').length == 0) {
-      // table doesn't have a tHead. Since it should have, create one and
-      // put the first table row in it.
-      the = document.createElement('thead');
-      the.appendChild(table.rows[0]);
-      table.insertBefore(the,table.firstChild);
-    }
-    // Safari doesn't support table.tHead, sigh
-    if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
-
-    if (table.tHead.rows.length != 1) return; // can't cope with two header rows
-
-    // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
-    // "total" rows, for example). This is B&R, since what you're supposed
-    // to do is put them in a tfoot. So, if there are sortbottom rows,
-    // for backwards compatibility, move them to tfoot (creating it if needed).
-    sortbottomrows = [];
-    for (var i=0; i<table.rows.length; i++) {
-      if (table.rows[i].className.search(/\bsortbottom\b/) != -1) {
-        sortbottomrows[sortbottomrows.length] = table.rows[i];
-      }
-    }
-    if (sortbottomrows) {
-      if (table.tFoot == null) {
-        // table doesn't have a tfoot. Create one.
-        tfo = document.createElement('tfoot');
-        table.appendChild(tfo);
-      }
-      for (var i=0; i<sortbottomrows.length; i++) {
-        tfo.appendChild(sortbottomrows[i]);
-      }
-      delete sortbottomrows;
-    }
-
-    // work through each column and calculate its type
-    headrow = table.tHead.rows[0].cells;
-    for (var i=0; i<headrow.length; i++) {
-      // manually override the type with a sorttable_type attribute
-      if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
-        mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
-        if (mtch) { override = mtch[1]; }
-	      if (mtch && typeof sorttable["sort_"+override] == 'function') {
-	        headrow[i].sorttable_sortfunction = sorttable["sort_"+override];
-	      } else {
-	        headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
-	      }
-	      // make it clickable to sort
-	      headrow[i].sorttable_columnindex = i;
-	      headrow[i].sorttable_tbody = table.tBodies[0];
-	      dean_addEvent(headrow[i],"click", sorttable.innerSortFunction = function(e) {
-
-          if (this.className.search(/\bsorttable_sorted\b/) != -1) {
-            // if we're already sorted by this column, just
-            // reverse the table, which is quicker
-            sorttable.reverse(this.sorttable_tbody);
-            this.className = this.className.replace('sorttable_sorted',
-                                                    'sorttable_sorted_reverse');
-            this.removeChild(document.getElementById('sorttable_sortfwdind'));
-            sortrevind = document.createElement('span');
-            sortrevind.id = "sorttable_sortrevind";
-            sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
-            this.appendChild(sortrevind);
-            return;
-          }
-          if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
-            // if we're already sorted by this column in reverse, just
-            // re-reverse the table, which is quicker
-            sorttable.reverse(this.sorttable_tbody);
-            this.className = this.className.replace('sorttable_sorted_reverse',
-                                                    'sorttable_sorted');
-            this.removeChild(document.getElementById('sorttable_sortrevind'));
-            sortfwdind = document.createElement('span');
-            sortfwdind.id = "sorttable_sortfwdind";
-            sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
-            this.appendChild(sortfwdind);
-            return;
-          }
-
-          // remove sorttable_sorted classes
-          theadrow = this.parentNode;
-          forEach(theadrow.childNodes, function(cell) {
-            if (cell.nodeType == 1) { // an element
-              cell.className = cell.className.replace('sorttable_sorted_reverse','');
-              cell.className = cell.className.replace('sorttable_sorted','');
-            }
-          });
-          sortfwdind = document.getElementById('sorttable_sortfwdind');
-          if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
-          sortrevind = document.getElementById('sorttable_sortrevind');
-          if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
-
-          this.className += ' sorttable_sorted';
-          sortfwdind = document.createElement('span');
-          sortfwdind.id = "sorttable_sortfwdind";
-          sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
-          this.appendChild(sortfwdind);
-
-	        // build an array to sort. This is a Schwartzian transform thing,
-	        // i.e., we "decorate" each row with the actual sort key,
-	        // sort based on the sort keys, and then put the rows back in order
-	        // which is a lot faster because you only do getInnerText once per row
-	        row_array = [];
-	        col = this.sorttable_columnindex;
-	        rows = this.sorttable_tbody.rows;
-	        for (var j=0; j<rows.length; j++) {
-	          row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];
-	        }
-	        /* If you want a stable sort, uncomment the following line */
-	        //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
-	        /* and comment out this one */
-	        row_array.sort(this.sorttable_sortfunction);
-
-	        tb = this.sorttable_tbody;
-	        for (var j=0; j<row_array.length; j++) {
-	          tb.appendChild(row_array[j][1]);
-	        }
-
-	        delete row_array;
-	      });
-	    }
-    }
-  },
-
-  guessType: function(table, column) {
-    // guess the type of a column based on its first non-blank row
-    sortfn = sorttable.sort_alpha;
-    for (var i=0; i<table.tBodies[0].rows.length; i++) {
-      text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
-      if (text != '') {
-        if (text.match(/^-?[£$¤]?[\d,.]+%?$/)) {
-          return sorttable.sort_numeric;
-        }
-        // check for a date: dd/mm/yyyy or dd/mm/yy
-        // can have / or . or - as separator
-        // can be mm/dd as well
-        possdate = text.match(sorttable.DATE_RE)
-        if (possdate) {
-          // looks like a date
-          first = parseInt(possdate[1]);
-          second = parseInt(possdate[2]);
-          if (first > 12) {
-            // definitely dd/mm
-            return sorttable.sort_ddmm;
-          } else if (second > 12) {
-            return sorttable.sort_mmdd;
-          } else {
-            // looks like a date, but we can't tell which, so assume
-            // that it's dd/mm (English imperialism!) and keep looking
-            sortfn = sorttable.sort_ddmm;
-          }
-        }
-      }
-    }
-    return sortfn;
-  },
-
-  getInnerText: function(node) {
-    // gets the text we want to use for sorting for a cell.
-    // strips leading and trailing whitespace.
-    // this is *not* a generic getInnerText function; it's special to sorttable.
-    // for example, you can override the cell text with a customkey attribute.
-    // it also gets .value for <input> fields.
-
-    if (!node) return "";
-
-    hasInputs = (typeof node.getElementsByTagName == 'function') &&
-                 node.getElementsByTagName('input').length;
-
-    if (node.getAttribute("sorttable_customkey") != null) {
-      return node.getAttribute("sorttable_customkey");
-    }
-    else if (typeof node.textContent != 'undefined' && !hasInputs) {
-      return node.textContent.replace(/^\s+|\s+$/g, '');
-    }
-    else if (typeof node.innerText != 'undefined' && !hasInputs) {
-      return node.innerText.replace(/^\s+|\s+$/g, '');
-    }
-    else if (typeof node.text != 'undefined' && !hasInputs) {
-      return node.text.replace(/^\s+|\s+$/g, '');
-    }
-    else {
-      switch (node.nodeType) {
-        case 3:
-          if (node.nodeName.toLowerCase() == 'input') {
-            return node.value.replace(/^\s+|\s+$/g, '');
-          }
-        case 4:
-          return node.nodeValue.replace(/^\s+|\s+$/g, '');
-          break;
-        case 1:
-        case 11:
-          var innerText = '';
-          for (var i = 0; i < node.childNodes.length; i++) {
-            innerText += sorttable.getInnerText(node.childNodes[i]);
-          }
-          return innerText.replace(/^\s+|\s+$/g, '');
-          break;
-        default:
-          return '';
-      }
-    }
-  },
-
-  reverse: function(tbody) {
-    // reverse the rows in a tbody
-    newrows = [];
-    for (var i=0; i<tbody.rows.length; i++) {
-      newrows[newrows.length] = tbody.rows[i];
-    }
-    for (var i=newrows.length-1; i>=0; i--) {
-       tbody.appendChild(newrows[i]);
-    }
-    delete newrows;
-  },
-
-  /* sort functions
-     each sort function takes two parameters, a and b
-     you are comparing a[0] and b[0] */
-  sort_numeric: function(a,b) {
-    aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
-    if (isNaN(aa)) aa = 0;
-    bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
-    if (isNaN(bb)) bb = 0;
-    return aa-bb;
-  },
-  sort_alpha: function(a,b) {
-    if (a[0]==b[0]) return 0;
-    if (a[0]<b[0]) return -1;
-    return 1;
-  },
-  sort_ddmm: function(a,b) {
-    mtch = a[0].match(sorttable.DATE_RE);
-    y = mtch[3]; m = mtch[2]; d = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt1 = y+m+d;
-    mtch = b[0].match(sorttable.DATE_RE);
-    y = mtch[3]; m = mtch[2]; d = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt2 = y+m+d;
-    if (dt1==dt2) return 0;
-    if (dt1<dt2) return -1;
-    return 1;
-  },
-  sort_mmdd: function(a,b) {
-    mtch = a[0].match(sorttable.DATE_RE);
-    y = mtch[3]; d = mtch[2]; m = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt1 = y+m+d;
-    mtch = b[0].match(sorttable.DATE_RE);
-    y = mtch[3]; d = mtch[2]; m = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt2 = y+m+d;
-    if (dt1==dt2) return 0;
-    if (dt1<dt2) return -1;
-    return 1;
-  },
-
-  shaker_sort: function(list, comp_func) {
-    // A stable sort function to allow multi-level sorting of data
-    // see: http://en.wikipedia.org/wiki/Cocktail_sort
-    // thanks to Joseph Nahmias
-    var b = 0;
-    var t = list.length - 1;
-    var swap = true;
-
-    while(swap) {
-        swap = false;
-        for(var i = b; i < t; ++i) {
-            if ( comp_func(list[i], list[i+1]) > 0 ) {
-                var q = list[i]; list[i] = list[i+1]; list[i+1] = q;
-                swap = true;
-            }
-        } // for
-        t--;
-
-        if (!swap) break;
-
-        for(var i = t; i > b; --i) {
-            if ( comp_func(list[i], list[i-1]) < 0 ) {
-                var q = list[i]; list[i] = list[i-1]; list[i-1] = q;
-                swap = true;
-            }
-        } // for
-        b++;
-
-    } // while(swap)
-  }
-}
-
-/* ******************************************************************
-   Supporting functions: bundled here to avoid depending on a library
-   ****************************************************************** */
-
-// Dean Edwards/Matthias Miller/John Resig
-
-/* for Mozilla/Opera9 */
-if (document.addEventListener) {
-    document.addEventListener("DOMContentLoaded", sorttable.init, false);
-}
-
-/* for Internet Explorer */
-/*@cc_on @*/
-/*@if (@_win32)
-    document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
-    var script = document.getElementById("__ie_onload");
-    script.onreadystatechange = function() {
-        if (this.readyState == "complete") {
-            sorttable.init(); // call the onload handler
-        }
-    };
-/*@end @*/
-
-/* for Safari */
-if (/WebKit/i.test(navigator.userAgent)) { // sniff
-    var _timer = setInterval(function() {
-        if (/loaded|complete/.test(document.readyState)) {
-            sorttable.init(); // call the onload handler
-        }
-    }, 10);
-}
-
-/* for other browsers */
-window.onload = sorttable.init;
-
-// written by Dean Edwards, 2005
-// with input from Tino Zijdel, Matthias Miller, Diego Perini
-
-// http://dean.edwards.name/weblog/2005/10/add-event/
-
-function dean_addEvent(element, type, handler) {
-	if (element.addEventListener) {
-		element.addEventListener(type, handler, false);
-	} else {
-		// assign each event handler a unique ID
-		if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;
-		// create a hash table of event types for the element
-		if (!element.events) element.events = {};
-		// create a hash table of event handlers for each element/event pair
-		var handlers = element.events[type];
-		if (!handlers) {
-			handlers = element.events[type] = {};
-			// store the existing event handler (if there is one)
-			if (element["on" + type]) {
-				handlers[0] = element["on" + type];
-			}
-		}
-		// store the event handler in the hash table
-		handlers[handler.$$guid] = handler;
-		// assign a global event handler to do all the work
-		element["on" + type] = handleEvent;
-	}
-};
-// a counter used to create unique IDs
-dean_addEvent.guid = 1;
-
-function removeEvent(element, type, handler) {
-	if (element.removeEventListener) {
-		element.removeEventListener(type, handler, false);
-	} else {
-		// delete the event handler from the hash table
-		if (element.events && element.events[type]) {
-			delete element.events[type][handler.$$guid];
-		}
-	}
-};
-
-function handleEvent(event) {
-	var returnValue = true;
-	// grab the event object (IE uses a global event object)
-	event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
-	// get a reference to the hash table of event handlers
-	var handlers = this.events[event.type];
-	// execute each event handler
-	for (var i in handlers) {
-		this.$$handleEvent = handlers[i];
-		if (this.$$handleEvent(event) === false) {
-			returnValue = false;
-		}
-	}
-	return returnValue;
-};
-
-function fixEvent(event) {
-	// add W3C standard event methods
-	event.preventDefault = fixEvent.preventDefault;
-	event.stopPropagation = fixEvent.stopPropagation;
-	return event;
-};
-fixEvent.preventDefault = function() {
-	this.returnValue = false;
-};
-fixEvent.stopPropagation = function() {
-  this.cancelBubble = true;
-}
-
-// Dean's forEach: http://dean.edwards.name/base/forEach.js
-/*
-	forEach, version 1.0
-	Copyright 2006, Dean Edwards
-	License: http://www.opensource.org/licenses/mit-license.php
-*/
-
-// array-like enumeration
-if (!Array.forEach) { // mozilla already supports this
-	Array.forEach = function(array, block, context) {
-		for (var i = 0; i < array.length; i++) {
-			block.call(context, array[i], i, array);
-		}
-	};
-}
-
-// generic enumeration
-Function.prototype.forEach = function(object, block, context) {
-	for (var key in object) {
-		if (typeof this.prototype[key] == "undefined") {
-			block.call(context, object[key], key, object);
-		}
-	}
-};
-
-// character enumeration
-String.forEach = function(string, block, context) {
-	Array.forEach(string.split(""), function(chr, index) {
-		block.call(context, chr, index, string);
-	});
-};
-
-// globally resolve forEach enumeration
-var forEach = function(object, block, context) {
-	if (object) {
-		var resolve = Object; // default
-		if (object instanceof Function) {
-			// functions have a "length" property
-			resolve = Function;
-		} else if (object.forEach instanceof Function) {
-			// the object implements a custom forEach method so use that
-			object.forEach(block, context);
-			return;
-		} else if (typeof object == "string") {
-			// the object is a string
-			resolve = String;
-		} else if (typeof object.length == "number") {
-			// the object is array-like
-			resolve = Array;
-		}
-		resolve.forEach(object, block, context);
-	}
-};
-
-
diff --git a/package.json b/package.json
index a82af1e..e3d4a67 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,6 @@
     "babel-preset-es2015": "^6.6.0",
     "chai": "^3.5.0",
     "expect": "^1.20.1",
-    "floatthead": "^1.4.0",
     "jasmine": "^2.4.1",
     "jasmine-core": "^2.4.1",
     "karma": "^0.13.22",
@@ -50,6 +49,7 @@
     "babelify": "^7.3.0",
     "browserify": "^13.0.1",
     "es6-mixins": "^1.0.2",
+    "floatthead": "^1.4.0",
     "glob": "^7.0.3",
     "gulp": "^3.9.1",
     "gulp-babel": "^6.1.2",
-- 
GitLab