diff --git a/html/include/tsorter.js b/html/include/tsorter.js
new file mode 100644
index 0000000000000000000000000000000000000000..dbac680353119f5d0a7835f20b2864ef6c095907
--- /dev/null
+++ b/html/include/tsorter.js
@@ -0,0 +1,249 @@
+/*!
+ * tsorter 2.0.0 - Copyright 2015 Terrill Dent, http://terrill.ca
+ * JavaScript HTML Table Sorter
+ * Released under MIT license, http://terrill.ca/sorting/tsorter/LICENSE
+ */
+var tsorter = (function()
+{
+    'use strict';
+
+    var sorterPrototype,
+        addEvent,
+        removeEvent,
+        hasEventListener = !!document.addEventListener;
+
+    if( !Object.create ){
+        // Define Missing Function
+        Object.create = function( prototype ) {
+            var Obj = function(){return undefined;};
+            Obj.prototype = prototype;
+            return new Obj();
+        };
+    }
+
+    // Cross Browser event binding
+    addEvent = function( element, eventName, callback ) {
+        if( hasEventListener ) {
+            element.addEventListener(eventName, callback, false );
+        } else {
+            element.attachEvent( 'on' + eventName, callback);
+        }
+    };
+
+    // Cross Browser event removal
+    removeEvent = function( element, eventName, callback ) {
+        if( hasEventListener ) {
+            element.removeEventListener(eventName, callback, false );
+        } else {
+            element.detachEvent( 'on' + eventName, callback);
+        }
+    };
+
+    sorterPrototype = {
+
+        getCell: function(row)
+        {
+            var that = this;
+            return that.trs[row].cells[that.column];
+        },
+
+        /* SORT
+         * Sorts a particular column. If it has been sorted then call reverse
+         * if not, then use quicksort to get it sorted.
+         * Sets the arrow direction in the headers.
+         * @param oTH - the table header cell (<th>) object that is clicked
+         */
+        sort: function( e )
+        {
+            var that = this,
+                th = e.target;
+
+            // TODO: make sure target 'th' is not a child element of a <th>
+            //  We can't use currentTarget because of backwards browser support
+            //  IE6,7,8 don't have it.
+
+            // set the data retrieval function for this column
+            that.column = th.cellIndex;
+            that.get = that.getAccessor( th.getAttribute('data-tsorter') );
+
+            if( that.prevCol === that.column )
+            {
+                // if already sorted, reverse
+                th.className = th.className !== 'ascend' ? 'ascend' : 'descend';
+                that.reverseTable();
+            }
+            else
+            {
+                // not sorted - call quicksort
+                th.className = 'ascend';
+                if( that.prevCol !== -1 && that.ths[that.prevCol].className !== 'exc_cell'){
+                    that.ths[that.prevCol].className = '';
+                }
+                that.quicksort(0, that.trs.length - 1);
+            }
+            that.prevCol = that.column;
+        },
+
+        /*
+         * Choose Data Accessor Function
+         * @param: the html structure type (from the data-type attribute)
+         */
+        getAccessor: function(sortType)
+        {
+            var that = this,
+                accessors = that.accessors;
+
+            if( accessors && accessors[ sortType ] ){
+                return accessors[ sortType ];
+            }
+
+            switch( sortType )
+            {
+                case "link":
+                    return function(row){
+                        return that.getCell(row).firstChild.firstChild.nodeValue;
+                    };
+                case "input":
+                    return function(row){
+                        return that.getCell(row).firstChild.value;
+                    };
+                case "numeric":
+                    return function(row){
+                        return parseFloat( that.getCell(row).firstChild.nodeValue, 10 );
+                    };
+                default: /* Plain Text */
+                    return function(row){
+                        return that.getCell(row).firstChild.nodeValue;
+                    };
+            }
+        },
+
+        /* Exchange
+         * A complicated way of exchanging two rows in a table.
+         * Exchanges rows at index i and j
+         */
+        exchange: function(i, j)
+        {
+            var that = this,
+                tbody = that.tbody,
+                trs = that.trs,
+                tmpNode;
+
+            if( i === j+1 ) {
+                tbody.insertBefore(trs[i], trs[j]);
+            } else if( j === i+1 ) {
+                tbody.insertBefore(trs[j], trs[i]);
+            } else {
+                tmpNode = tbody.replaceChild(trs[i], trs[j]);
+                if( !trs[i] ) {
+                    tbody.appendChild(tmpNode);
+                } else {
+                    tbody.insertBefore(tmpNode, trs[i]);
+                }
+            }
+        },
+
+        /*
+         * REVERSE TABLE
+         * Reverses a table ordering
+         */
+        reverseTable: function()
+        {
+            var that = this,
+                i;
+
+            for( i = 1; i < (that.trs.length - 1); i++ ) {
+                that.tbody.insertBefore( that.trs[i], that.trs[0] );
+            }
+        },
+
+        /*
+         * QUICKSORT
+         * @param: lo - the low index of the array to sort
+         * @param: hi - the high index of the array to sort
+         */
+        quicksort: function(lo, hi)
+        {
+            var i, j, pivot,
+                that = this;
+
+            if( hi <= lo+1 ){ return; }
+
+            if( (hi - lo) === 2 ) {
+                if(that.get(hi-1) > that.get(lo)) {
+                    that.exchange(hi-1, lo);
+                }
+                return;
+            }
+
+            i = lo + 1;
+            j = hi - 1;
+
+            if( that.get(lo) > that.get( i) ){ that.exchange( i, lo); }
+            if( that.get( j) > that.get(lo) ){ that.exchange(lo,  j); }
+            if( that.get(lo) > that.get( i) ){ that.exchange( i, lo); }
+
+            pivot = that.get(lo);
+
+            while(true) {
+                j--;
+                while(pivot > that.get(j)){ j--; }
+                i++;
+                while(that.get(i) > pivot){ i++; }
+                if(j <= i){ break; }
+                that.exchange(i, j);
+            }
+            that.exchange(lo, j);
+
+            if((j-lo) < (hi-j)) {
+                that.quicksort(lo, j);
+                that.quicksort(j+1, hi);
+            } else {
+                that.quicksort(j+1, hi);
+                that.quicksort(lo, j);
+            }
+        },
+
+        init: function( table, initialSortedColumn, customDataAccessors ){
+            var that = this,
+                i;
+
+            if( typeof table === 'string' ){
+                table = document.getElementById(table);
+            }
+
+            that.table = table;
+            that.ths   = table.getElementsByTagName("th");
+            that.tbody = table.tBodies[0];
+            that.trs   = that.tbody.getElementsByTagName("tr");
+            that.prevCol = ( initialSortedColumn && initialSortedColumn > 0 ) ? initialSortedColumn : -1;
+            that.accessors = customDataAccessors;
+            that.boundSort = that.sort.bind( that );
+
+            for( i = 0; i < that.ths.length; i++ ) {
+                addEvent( that.ths[i], 'click', that.boundSort );
+            }
+        },
+
+        destroy: function(){
+            var that = this,
+                i;
+
+            if( that.ths ){
+                for( i = 0; i < that.ths.length; i++ ) {
+                    removeEvent( that.ths[i], 'click', that.boundSort );
+                }
+            }
+        }
+    };
+
+    // Create a new sorter given a table element
+    return {
+        create: function( table, initialSortedColumn, customDataAccessors )
+        {
+            var sorter = Object.create( sorterPrototype );
+            sorter.init( table, initialSortedColumn, customDataAccessors );
+            return sorter;
+        }
+    };
+}());
diff --git a/html/themes/breezy/lists.css b/html/themes/breezy/lists.css
index 61734148fedc88db9f42fda47dc91d2d23c176e1..8824e4d91f2749915699f1ba5573987c0b2ceedf 100644
--- a/html/themes/breezy/lists.css
+++ b/html/themes/breezy/lists.css
@@ -31,6 +31,14 @@ html.rtl table.listingTable > thead > tr > th {
 border-left: 1px solid #c0c2c3;
 }
 
+/* Up and Down Arrows */
+.listingTable > thead > tr > th.descend:after {
+content: "\25B2";
+}
+.listingTable > thead > tr > th.ascend:after {
+content: "\25BC";
+}
+
 table.listingTable > tbody {
 height: 100%;
 overflow-x: hidden;
diff --git a/html/themes/legacy/lists.css b/html/themes/legacy/lists.css
index 7e9104e907a2ec65f5ab9fbc2c46d30531ff23fc..7819b924b8f8700ddc8bb50744f9a3fc6bc6c552 100644
--- a/html/themes/legacy/lists.css
+++ b/html/themes/legacy/lists.css
@@ -31,6 +31,14 @@ html.rtl table.listingTable > thead > tr > th {
 border-left: 1px solid #aaa;
 }
 
+/* Up and Down Arrows */
+.listingTable > thead > tr > th.descend:after {
+content: "\25B2";
+}
+.listingTable > thead > tr > th.ascend:after {
+content: "\25BC";
+}
+
 table.listingTable > tbody {
 height: 100%;
 overflow-x: hidden;
diff --git a/ihtml/themes/breezy/headers.tpl b/ihtml/themes/breezy/headers.tpl
index fc3bd139f70e4c27c7626516b7b5ba37252ed371..9753b1eae700a1cf03a78326fa3f05b2d488998b 100644
--- a/ihtml/themes/breezy/headers.tpl
+++ b/ihtml/themes/breezy/headers.tpl
@@ -26,6 +26,7 @@
   <script src="include/pulldown.js" type="text/javascript"></script>
   <script src="include/datepicker.js" type="text/javascript"></script>
 {/if}
+  <script src="include/tsorter.js" type="text/javascript"></script>
 {foreach from=$js_files item=file}
   <script src="{$file}" type="text/javascript"></script>
 {/foreach}
diff --git a/include/class_SnapshotDialogs.inc b/include/class_SnapshotDialogs.inc
index ea499d6cad05832ced805a89db0e0881913598e9..50437cc9be86c6bf60de22b8067ef976f4791300 100644
--- a/include/class_SnapshotDialogs.inc
+++ b/include/class_SnapshotDialogs.inc
@@ -85,6 +85,27 @@ class SnapshotsAttribute extends OrderedArrayAttribute
     $this->setInLdap(FALSE);
   }
 
+  function setParent (&$plugin)
+  {
+    parent::setParent($plugin);
+    if (is_object($this->plugin)) {
+      if ($this->plugin->global) {
+        $this->setHeaders(array(
+          _('DN'),
+          _('Date'),
+          _('Description'),
+          ''
+        ));
+      } else {
+        $this->setHeaders(array(
+          _('Date'),
+          _('Description'),
+          ''
+        ));
+      }
+    }
+  }
+
   function loadPostValue ()
   {
     if ($this->isVisible()) {
@@ -193,10 +214,10 @@ class SnapshotRestoreDialog extends simplePlugin
 
   function __construct ($dn, $parent, $global, $aclCategory)
   {
+    $this->global       = $global;
     parent::__construct();
     $this->object_dn    = $dn;
     $this->parent       = $parent;
-    $this->global       = $global;
     $this->aclCategory  = $aclCategory;
     if ($this->global) {
       $this->attributesAccess['object_dn']->setVisible(FALSE);
diff --git a/include/class_divSelectBox.inc b/include/class_divSelectBox.inc
index f580e795054df82f09c90e9b355ff7d5f4861a71..722a8daaf62168930796f83f831ef2ae77121f9a 100644
--- a/include/class_divSelectBox.inc
+++ b/include/class_divSelectBox.inc
@@ -105,6 +105,12 @@ class divSelectBox
                   ">\n";
     $s_return .= $this->_generatePage();
     $s_return .= '</table></div></div>';
+    if ($this->headers !== FALSE) {
+      $s_return .=
+        '<script type="text/javascript">
+          var sorter'.$this->id.' = tsorter.create(\''.$this->id.'\');
+        </script>';
+    }
     return $s_return;
   }