diff --git a/libraries.txt b/libraries.txt
index 7e0bd5b7..a8c634c8 100644
--- a/libraries.txt
+++ b/libraries.txt
@@ -31,6 +31,6 @@ dropzone            4e20bd4     MIT          https://github.com/enyo/dropzone
 Filemanager         7e060c2     MIT          https://github.com/simogeo/Filemanager
 Unit (Length)       d8e6237     MIT          https://github.com/heygrady/Units/blob/master/Length.min.js
 Natural Sort        9565816     MIT          https://github.com/javve/natural-sort/blob/master/index.js
-SlickGrid           2.2.4       MIT          https://github.com/6pac/SlickGrid
+SlickGrid          2.3.6-53ee34 MIT          https://github.com/6pac/SlickGrid
 jQuery-UI           1.11.3      MIT          https://jqueryui.com/
 BigNumber           3.0.1       MIT          http://mikemcl.github.io/bignumber.js
\ No newline at end of file
diff --git a/web/pgadmin/static/vendor/slickgrid/README b/web/pgadmin/static/vendor/slickgrid/README
deleted file mode 100644
index cf583515..00000000
--- a/web/pgadmin/static/vendor/slickgrid/README
+++ /dev/null
@@ -1,9 +0,0 @@
-WARNING!!
-
-The following changes have been made to SlickGrid. These must be re-applied if updating the code from upstream!
-
-- The CSS class 'slick-row' has been renamed to 'sr'
-
-- The CSS class 'slick-cell' has been renamed to 'sc'
-
-The intent of these changes is to minimise memory usage by the grid, by saving a few bytes per row/cell.
\ No newline at end of file
diff --git a/web/pgadmin/static/vendor/slickgrid/README.md b/web/pgadmin/static/vendor/slickgrid/README.md
new file mode 100644
index 00000000..e8a1ad7a
--- /dev/null
+++ b/web/pgadmin/static/vendor/slickgrid/README.md
@@ -0,0 +1,9 @@
+## This is the 6pac SlickGrid repo
+
+This is the acknowledged most active non-customised fork of SlickGrid.
+
+It aims to be a viable alternative master repo, building on the legacy of the mleibman/SlickGrid master branch, keeping libraries up to date and applying small, safe core patches and enhancements without turning into a personalised build.
+
+Check out the [examples](https://github.com/6pac/SlickGrid/wiki/Examples) for examples demonstrating new features and use cases, such as dynamic grid creation and editors with third party controls.
+
+Also check out the [wiki](https://github.com/6pac/SlickGrid/wiki) for news and documentation.
\ No newline at end of file
diff --git a/web/pgadmin/static/vendor/slickgrid/controls/slick.columnpicker.js b/web/pgadmin/static/vendor/slickgrid/controls/slick.columnpicker.js
index dc167209..93a6be5b 100644
--- a/web/pgadmin/static/vendor/slickgrid/controls/slick.columnpicker.js
+++ b/web/pgadmin/static/vendor/slickgrid/controls/slick.columnpicker.js
@@ -12,12 +12,12 @@
       grid.onColumnsReordered.subscribe(updateColumnOrder);
       options = $.extend({}, defaults, options);
 
-      $menu = $("<span class='slick-columnpicker' style='display:none;position:absolute;z-index:20;' />").appendTo(document.body);
+      $menu = $("<span class='slick-columnpicker' style='display:none;position:absolute;z-index:20;overflow-y:scroll;' />").appendTo(document.body);
 
-      $menu.bind("mouseleave", function (e) {
+      $menu.on("mouseleave", function (e) {
         $(this).fadeOut(options.fadeSpeed)
       });
-      $menu.bind("click", updateColumn);
+      $menu.on("click", updateColumn);
 
     }
 
@@ -44,7 +44,7 @@
         }
 
         $("<label />")
-            .text(columns[i].name)
+            .html(columns[i].name)
             .prepend($input)
             .appendTo($li);
       }
@@ -73,6 +73,7 @@
       $menu
           .css("top", e.pageY - 10)
           .css("left", e.pageX - 10)
+          .css("max-height", $(window).height() - e.pageY -10)
           .fadeIn(options.fadeSpeed);
     }
 
diff --git a/web/pgadmin/static/vendor/slickgrid/images/CheckboxN.png b/web/pgadmin/static/vendor/slickgrid/images/CheckboxN.png
new file mode 100644
index 00000000..072ecbb9
Binary files /dev/null and b/web/pgadmin/static/vendor/slickgrid/images/CheckboxN.png differ
diff --git a/web/pgadmin/static/vendor/slickgrid/images/CheckboxY.png b/web/pgadmin/static/vendor/slickgrid/images/CheckboxY.png
new file mode 100644
index 00000000..467f3010
Binary files /dev/null and b/web/pgadmin/static/vendor/slickgrid/images/CheckboxY.png differ
diff --git a/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellcopymanager.js b/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellcopymanager.js
index c74018de..66c2376c 100644
--- a/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellcopymanager.js
+++ b/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellcopymanager.js
@@ -46,10 +46,12 @@
         if (e.which == 86 && (e.ctrlKey || e.metaKey)) {
           if (_copiedRanges) {
             e.preventDefault();
-            clearCopySelection();
             ranges = _grid.getSelectionModel().getSelectedRanges();
             _self.onPasteCells.notify({from: _copiedRanges, to: ranges});
-            _copiedRanges = null;
+            if (!_grid.getOptions().preserveCopiedSelectionOnPaste) {
+              clearCopySelection();
+              _copiedRanges = null;
+            }
           }
         }
       }
diff --git a/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellexternalcopymanager.js b/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellexternalcopymanager.js
new file mode 100644
index 00000000..fec7cb2a
--- /dev/null
+++ b/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellexternalcopymanager.js
@@ -0,0 +1,449 @@
+(function ($) {
+  // register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "CellExternalCopyManager": CellExternalCopyManager
+    }
+  });
+
+
+  function CellExternalCopyManager(options) {
+    /*
+      This manager enables users to copy/paste data from/to an external Spreadsheet application
+      such as MS-Excel® or OpenOffice-Spreadsheet.
+      
+      Since it is not possible to access directly the clipboard in javascript, the plugin uses
+      a trick to do it's job. After detecting the keystroke, we dynamically create a textarea
+      where the browser copies/pastes the serialized data. 
+      
+      options:
+        copiedCellStyle : sets the css className used for copied cells. default : "copied"
+        copiedCellStyleLayerKey : sets the layer key for setting css values of copied cells. default : "copy-manager"
+        dataItemColumnValueExtractor : option to specify a custom column value extractor function
+        dataItemColumnValueSetter : option to specify a custom column value setter function
+        clipboardCommandHandler : option to specify a custom handler for paste actions
+        includeHeaderWhenCopying : set to true and the plugin will take the name property from each column (which is usually what appears in your header) and put that as the first row of the text that's copied to the clipboard
+        bodyElement: option to specify a custom DOM element which to will be added the hidden textbox. It's useful if the grid is inside a modal dialog.
+        onCopyInit: optional handler to run when copy action initializes
+        onCopySuccess: optional handler to run when copy action is complete
+        newRowCreator: function to add rows to table if paste overflows bottom of table
+        readOnlyMode: suppresses paste
+    */
+    var _grid;
+    var _self = this;
+    var _copiedRanges;
+    var _options = options || {};
+    var _copiedCellStyleLayerKey = _options.copiedCellStyleLayerKey || "copy-manager";
+    var _copiedCellStyle = _options.copiedCellStyle || "copied";
+    var _clearCopyTI = 0;
+    var _bodyElement = _options.bodyElement || document.body;
+    var _onCopyInit = _options.onCopyInit || null;
+    var _onCopySuccess = _options.onCopySuccess || null;
+    
+    var keyCodes = {
+      'C': 67,
+      'V': 86,
+      'ESC': 27,
+      'INSERT': 45
+    };
+
+    function init(grid) {
+      _grid = grid;
+      _grid.onKeyDown.subscribe(handleKeyDown);
+      
+      // we need a cell selection model
+      var cellSelectionModel = grid.getSelectionModel();
+      if (!cellSelectionModel){
+        throw new Error("Selection model is mandatory for this plugin. Please set a selection model on the grid before adding this plugin: grid.setSelectionModel(new Slick.CellSelectionModel())");
+      }
+      // we give focus on the grid when a selection is done on it.
+      // without this, if the user selects a range of cell without giving focus on a particular cell, the grid doesn't get the focus and key stroke handles (ctrl+c) don't work
+      cellSelectionModel.onSelectedRangesChanged.subscribe(function(e, args){
+        _grid.focus();
+      });
+    }
+
+    function destroy() {
+      _grid.onKeyDown.unsubscribe(handleKeyDown);
+    }
+    
+    function getDataItemValueForColumn(item, columnDef) {
+      if (_options.dataItemColumnValueExtractor) {
+        var dataItemColumnValueExtractorValue = _options.dataItemColumnValueExtractor(item, columnDef);
+
+        if (dataItemColumnValueExtractorValue)
+          return dataItemColumnValueExtractorValue;
+      }
+
+      var retVal = '';
+
+      // if a custom getter is not defined, we call serializeValue of the editor to serialize
+      if (columnDef.editor){
+        var editorArgs = {
+          'container':$("<p>"),  // a dummy container
+          'column':columnDef,
+          'position':{'top':0, 'left':0},  // a dummy position required by some editors
+          'grid':_grid
+        };
+        var editor = new columnDef.editor(editorArgs);
+        editor.loadValue(item);
+        retVal = editor.serializeValue();
+        editor.destroy();
+      }
+      else {
+        retVal = item[columnDef.field];
+      }
+
+      return retVal;
+    }
+    
+    function setDataItemValueForColumn(item, columnDef, value) {
+      if (_options.dataItemColumnValueSetter) {
+        return _options.dataItemColumnValueSetter(item, columnDef, value);
+      }
+
+      // if a custom setter is not defined, we call applyValue of the editor to unserialize
+      if (columnDef.editor){
+        var editorArgs = {
+          'container':$("body"),  // a dummy container
+          'column':columnDef,
+          'position':{'top':0, 'left':0},  // a dummy position required by some editors
+          'grid':_grid
+        };
+        var editor = new columnDef.editor(editorArgs);
+        editor.loadValue(item);
+        editor.applyValue(item, value);
+        editor.destroy();
+      }
+    }
+    
+    
+    function _createTextBox(innerText){
+      var ta = document.createElement('textarea');
+      ta.style.position = 'absolute';
+      ta.style.left = '-1000px';
+      ta.style.top = document.body.scrollTop + 'px';
+      ta.value = innerText;
+      _bodyElement.appendChild(ta);
+      ta.select();
+      
+      return ta;
+    }
+    
+    function _decodeTabularData(_grid, ta){
+      var columns = _grid.getColumns();
+      var clipText = ta.value;
+      var clipRows = clipText.split(/[\n\f\r]/);
+      // trim trailing CR if present
+      if (clipRows[clipRows.length - 1]=="") { clipRows.pop(); }
+      
+      var clippedRange = [];
+      var j = 0;
+      
+      _bodyElement.removeChild(ta);
+      for (var i=0; i<clipRows.length; i++) {
+        if (clipRows[i]!="")
+          clippedRange[j++] = clipRows[i].split("\t");
+          else
+          clippedRange[i] = [""];
+      }
+      var selectedCell = _grid.getActiveCell();
+      var ranges = _grid.getSelectionModel().getSelectedRanges();
+      var selectedRange = ranges && ranges.length ? ranges[0] : null;   // pick only one selection
+      var activeRow = null;
+      var activeCell = null;
+      
+      if (selectedRange){
+        activeRow = selectedRange.fromRow;
+        activeCell = selectedRange.fromCell;
+      } else if (selectedCell){
+        activeRow = selectedCell.row;
+        activeCell = selectedCell.cell;
+      } else {
+        // we don't know where to paste
+        return;
+      }
+      
+      var oneCellToMultiple = false;
+      var destH = clippedRange.length;
+      var destW = clippedRange.length ? clippedRange[0].length : 0;
+      if (clippedRange.length == 1 && clippedRange[0].length == 1 && selectedRange){
+        oneCellToMultiple = true;
+        destH = selectedRange.toRow - selectedRange.fromRow +1;
+        destW = selectedRange.toCell - selectedRange.fromCell +1;
+      }
+      var availableRows = _grid.getData().length - activeRow;
+      var addRows = 0;
+      if(availableRows < destH)
+      {
+        var d = _grid.getData();
+        for(addRows = 1; addRows <= destH - availableRows; addRows++)
+          d.push({});
+        _grid.setData(d);
+        _grid.render();
+      }
+
+      var overflowsBottomOfGrid = activeRow + destH > _grid.getDataLength();
+
+      if (_options.newRowCreator && overflowsBottomOfGrid) {
+
+        var newRowsNeeded = activeRow + destH - _grid.getDataLength();
+
+        _options.newRowCreator(newRowsNeeded);
+
+      }
+
+      var clipCommand = {
+
+        isClipboardCommand: true,
+        clippedRange: clippedRange,
+        oldValues: [],
+        cellExternalCopyManager: _self,
+        _options: _options,
+        setDataItemValueForColumn: setDataItemValueForColumn,
+        markCopySelection: markCopySelection,
+        oneCellToMultiple: oneCellToMultiple,
+        activeRow: activeRow,
+        activeCell: activeCell,
+        destH: destH,
+        destW: destW,
+        maxDestY: _grid.getDataLength(),
+        maxDestX: _grid.getColumns().length,
+        h: 0,
+        w: 0,
+          
+        execute: function() {
+          this.h=0;
+          for (var y = 0; y < this.destH; y++){
+            this.oldValues[y] = [];
+            this.w=0;
+            this.h++;
+            for (var x = 0; x < this.destW; x++){
+              this.w++;
+              var desty = activeRow + y;
+              var destx = activeCell + x;
+              
+              if (desty < this.maxDestY && destx < this.maxDestX ) {
+                var nd = _grid.getCellNode(desty, destx);
+                var dt = _grid.getDataItem(desty);
+                this.oldValues[y][x] = dt[columns[destx]['field']];
+                if (oneCellToMultiple)
+                  this.setDataItemValueForColumn(dt, columns[destx], clippedRange[0][0]);
+                else
+                  this.setDataItemValueForColumn(dt, columns[destx], clippedRange[y] ? clippedRange[y][x] : '');
+                _grid.updateCell(desty, destx);
+                _grid.onCellChange.notify({
+                    row: desty,
+                    cell: destx,
+                    item: dt,
+                    grid: _grid
+                });
+
+              }
+            }
+          }
+          
+          var bRange = {
+            'fromCell': activeCell,
+            'fromRow': activeRow,
+            'toCell': activeCell+this.w-1,
+            'toRow': activeRow+this.h-1
+          }
+
+          this.markCopySelection([bRange]);
+          _grid.getSelectionModel().setSelectedRanges([bRange]);
+          this.cellExternalCopyManager.onPasteCells.notify({ranges: [bRange]});
+        },
+
+        undo: function() {
+          for (var y = 0; y < this.destH; y++){
+            for (var x = 0; x < this.destW; x++){
+              var desty = activeRow + y;
+              var destx = activeCell + x;
+              
+              if (desty < this.maxDestY && destx < this.maxDestX ) {
+                var nd = _grid.getCellNode(desty, destx);
+                var dt = _grid.getDataItem(desty);
+                if (oneCellToMultiple)
+                  this.setDataItemValueForColumn(dt, columns[destx], this.oldValues[0][0]);
+                else
+                  this.setDataItemValueForColumn(dt, columns[destx], this.oldValues[y][x]);
+                _grid.updateCell(desty, destx);
+                _grid.onCellChange.notify({
+                    row: desty,
+                    cell: destx,
+                    item: dt,
+                    grid: _grid
+                });
+              }
+            }
+          }
+          
+          var bRange = {
+            'fromCell': activeCell,
+            'fromRow': activeRow,
+            'toCell': activeCell+this.w-1,
+            'toRow': activeRow+this.h-1
+          }
+
+          this.markCopySelection([bRange]);
+          _grid.getSelectionModel().setSelectedRanges([bRange]);
+          this.cellExternalCopyManager.onPasteCells.notify({ranges: [bRange]});
+          
+          if(addRows > 1){            
+            var d = _grid.getData();
+            for(; addRows > 1; addRows--)
+              d.splice(d.length - 1, 1);
+            _grid.setData(d);
+            _grid.render();
+          }
+        }
+      };
+
+      if(_options.clipboardCommandHandler) {
+        _options.clipboardCommandHandler(clipCommand);
+      }
+      else {
+        clipCommand.execute();
+      }
+    }
+    
+    
+    function handleKeyDown(e, args) {
+      var ranges;
+      if (!_grid.getEditorLock().isActive() || _grid.getOptions().autoEdit) {
+        if (e.which == keyCodes.ESC) {
+          if (_copiedRanges) {
+            e.preventDefault();
+            clearCopySelection();
+            _self.onCopyCancelled.notify({ranges: _copiedRanges});
+            _copiedRanges = null;
+          }
+        }
+
+        if ((e.which === keyCodes.C || e.which === keyCodes.INSERT) && (e.ctrlKey || e.metaKey) && !e.shiftKey) {    // CTRL+C or CTRL+INS
+          if (_onCopyInit) {
+            _onCopyInit.call();
+          }
+          ranges = _grid.getSelectionModel().getSelectedRanges();
+          if (ranges.length != 0) {
+            _copiedRanges = ranges;
+            markCopySelection(ranges);
+            _self.onCopyCells.notify({ranges: ranges});
+            
+            var columns = _grid.getColumns();
+            var clipText = "";
+
+            for (var rg = 0; rg < ranges.length; rg++){
+                var range = ranges[rg];
+                var clipTextRows = [];
+                for (var i=range.fromRow; i< range.toRow+1 ; i++){
+                    var clipTextCells = [];
+                    var dt = _grid.getDataItem(i);
+                    
+                    if (clipTextRows == "" && _options.includeHeaderWhenCopying) {
+                        var clipTextHeaders = [];
+                        for (var j = range.fromCell; j < range.toCell + 1 ; j++) {
+                            if (columns[j].name.length > 0)
+                                clipTextHeaders.push(columns[j].name);
+                        }
+                        clipTextRows.push(clipTextHeaders.join("\t"));
+                    }
+
+                    for (var j=range.fromCell; j< range.toCell+1 ; j++){
+                        clipTextCells.push(getDataItemValueForColumn(dt, columns[j]));
+                    }
+                    clipTextRows.push(clipTextCells.join("\t"));
+                }
+                clipText += clipTextRows.join("\r\n") + "\r\n";
+            }
+            
+            if(window.clipboardData) {
+                window.clipboardData.setData("Text", clipText);
+                return true;
+            }
+            else {
+                var focusEl = document.activeElement;
+
+                var ta = _createTextBox(clipText);
+
+                ta.focus();
+                
+                setTimeout(function(){
+                     _bodyElement.removeChild(ta);
+                    // restore focus
+                    if (focusEl)
+                        focusEl.focus();
+                    else
+                        console.log("Not element to restore focus to after copy?");
+
+                }, 100);
+
+                if (_onCopySuccess) {
+                    var rowCount = 0;
+                    // If it's cell selection, use the toRow/fromRow fields
+                    if (ranges.length === 1) {
+                        rowCount = (ranges[0].toRow + 1) - ranges[0].fromRow;
+                    }
+                    else {
+                        rowCount = ranges.length;
+                    }
+                    _onCopySuccess.call(this, rowCount);
+                }
+
+                return false;
+            }
+          }
+        }
+
+        if (!_options.readOnlyMode && (
+         (e.which === keyCodes.V && (e.ctrlKey || e.metaKey) && !e.shiftKey)
+          || (e.which === keyCodes.INSERT && e.shiftKey && !e.ctrlKey)
+         )) {    // CTRL+V or Shift+INS
+            var ta = _createTextBox('');
+            
+            setTimeout(function(){
+                _decodeTabularData(_grid, ta);
+            }, 100);
+            
+            return false;
+        }
+      }
+    }
+
+    function markCopySelection(ranges) {
+      clearCopySelection();
+      
+      var columns = _grid.getColumns();
+      var hash = {};
+      for (var i = 0; i < ranges.length; i++) {
+        for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
+          hash[j] = {};
+          for (var k = ranges[i].fromCell; k <= ranges[i].toCell && k<columns.length; k++) {
+            hash[j][columns[k].id] = _copiedCellStyle;
+          }
+        }
+      }
+      _grid.setCellCssStyles(_copiedCellStyleLayerKey, hash);
+      clearTimeout(_clearCopyTI);
+      _clearCopyTI = setTimeout(function(){
+        _self.clearCopySelection();
+      }, 2000);
+    }
+
+    function clearCopySelection() {
+      _grid.removeCellCssStyles(_copiedCellStyleLayerKey);
+    }
+
+    $.extend(this, {
+      "init": init,
+      "destroy": destroy,
+      "clearCopySelection": clearCopySelection,
+      "handleKeyDown":handleKeyDown,
+      
+      "onCopyCells": new Slick.Event(),
+      "onCopyCancelled": new Slick.Event(),
+      "onPasteCells": new Slick.Event()
+    });
+  }
+})(jQuery);
diff --git a/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellrangeselector.js b/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellrangeselector.js
index 520b17f3..8d97d6bf 100644
--- a/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellrangeselector.js
+++ b/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellrangeselector.js
@@ -10,6 +10,7 @@
   function CellRangeSelector(options) {
     var _grid;
     var _canvas;
+    var _currentlySelectedRange;
     var _dragging;
     var _decorator;
     var _self = this;
@@ -61,7 +62,7 @@
           dd.startY - $(_canvas).offset().top);
 
       dd.range = {start: start, end: {}};
-
+      _currentlySelectedRange = dd.range;
       return _decorator.show(new Slick.Range(start.row, start.cell));
     }
 
@@ -80,6 +81,7 @@
       }
 
       dd.range.end = end;
+      _currentlySelectedRange = dd.range;
       _decorator.show(new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell));
     }
 
@@ -102,9 +104,14 @@
       });
     }
 
+    function getCurrentRange() {
+      return _currentlySelectedRange;
+    }
+
     $.extend(this, {
       "init": init,
       "destroy": destroy,
+      "getCurrentRange": getCurrentRange,
 
       "onBeforeCellRangeSelected": new Slick.Event(),
       "onCellRangeSelected": new Slick.Event()
diff --git a/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellselectionmodel.js b/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellselectionmodel.js
index 07453ed1..47cb0cf6 100644
--- a/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellselectionmodel.js
+++ b/web/pgadmin/static/vendor/slickgrid/plugins/slick.cellselectionmodel.js
@@ -56,7 +56,7 @@
     }
 
     function setSelectedRanges(ranges) {
-      // simle check for: empty selection didn't change, prevent firing onSelectedRangesChanged
+      // simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged
       if ((!_ranges || _ranges.length === 0) && (!ranges || ranges.length === 0)) { return; }
 
       _ranges = removeInvalidRanges(ranges);
@@ -75,6 +75,7 @@
     }
 
     function handleCellRangeSelected(e, args) {
+      _grid.setActiveCell(args.range.fromRow, args.range.fromCell, false, false, true);
       setSelectedRanges([args.range]);
     }
 
@@ -94,8 +95,9 @@
        */                                         
       var ranges, last;
       var active = _grid.getActiveCell();
+      var metaKey = e.ctrlKey || e.metaKey;
 
-      if ( active && e.shiftKey && !e.ctrlKey && !e.altKey &&
+      if ( active && e.shiftKey && !metaKey && !e.altKey &&
           (e.which == 37 || e.which == 39 || e.which == 38 || e.which == 40) ) {
       
         ranges = getSelectedRanges();
diff --git a/web/pgadmin/static/vendor/slickgrid/plugins/slick.headerbuttons.js b/web/pgadmin/static/vendor/slickgrid/plugins/slick.headerbuttons.js
index 8e612735..d7c06195 100644
--- a/web/pgadmin/static/vendor/slickgrid/plugins/slick.headerbuttons.js
+++ b/web/pgadmin/static/vendor/slickgrid/plugins/slick.headerbuttons.js
@@ -122,11 +122,11 @@
           }
 
           if (button.handler) {
-            btn.bind("click", button.handler);
+            btn.on("click", button.handler);
           }
 
           btn
-            .bind("click", handleButtonClick)
+            .on("click", handleButtonClick)
             .appendTo(args.node);
         }
       }
diff --git a/web/pgadmin/static/vendor/slickgrid/plugins/slick.headermenu.js b/web/pgadmin/static/vendor/slickgrid/plugins/slick.headermenu.js
index ec8244da..c4032301 100644
--- a/web/pgadmin/static/vendor/slickgrid/plugins/slick.headermenu.js
+++ b/web/pgadmin/static/vendor/slickgrid/plugins/slick.headermenu.js
@@ -100,13 +100,13 @@
       _grid.setColumns(_grid.getColumns());
 
       // Hide the menu on outside click.
-      $(document.body).bind("mousedown", handleBodyMouseDown);
+      $(document.body).on("mousedown", handleBodyMouseDown);
     }
 
 
     function destroy() {
       _handler.unsubscribeAll();
-      $(document.body).unbind("mousedown", handleBodyMouseDown);
+      $(document.body).off("mousedown", handleBodyMouseDown);
     }
 
 
@@ -149,7 +149,7 @@
         }
 
         $el
-          .bind("click", showMenu)
+          .on("click", showMenu)
           .appendTo(args.node);
       }
     }
@@ -195,7 +195,7 @@
           .data("command", item.command || '')
           .data("column", columnDef)
           .data("item", item)
-          .bind("click", handleMenuItemClick)
+          .on("click", handleMenuItemClick)
           .appendTo($menu);
 
         if (item.disabled) {
diff --git a/web/pgadmin/static/vendor/slickgrid/plugins/slick.rowselectionmodel.js b/web/pgadmin/static/vendor/slickgrid/plugins/slick.rowselectionmodel.js
index 190b0f71..8553d050 100644
--- a/web/pgadmin/static/vendor/slickgrid/plugins/slick.rowselectionmodel.js
+++ b/web/pgadmin/static/vendor/slickgrid/plugins/slick.rowselectionmodel.js
@@ -121,8 +121,8 @@
 
         if (active >= 0 && active < _grid.getDataLength()) {
           _grid.scrollRowIntoView(active);
-          _ranges = rowsToRanges(getRowsRange(top, bottom));
-          setSelectedRanges(_ranges);
+          var tempRanges = rowsToRanges(getRowsRange(top, bottom));
+          setSelectedRanges(tempRanges);
         }
 
         e.preventDefault();
@@ -166,8 +166,8 @@
         _grid.setActiveCell(cell.row, cell.cell);
       }
 
-      _ranges = rowsToRanges(selection);
-      setSelectedRanges(_ranges);
+      var tempRanges = rowsToRanges(selection);
+      setSelectedRanges(tempRanges);
       e.stopImmediatePropagation();
 
       return true;
diff --git a/web/pgadmin/static/vendor/slickgrid/slick-default-theme.css b/web/pgadmin/static/vendor/slickgrid/slick-default-theme.css
index 2338d6e7..efc74154 100644
--- a/web/pgadmin/static/vendor/slickgrid/slick-default-theme.css
+++ b/web/pgadmin/static/vendor/slickgrid/slick-default-theme.css
@@ -29,23 +29,23 @@ classes should alter those!
   height: 100%;
 }
 
-.sr.ui-state-active {
+.slick-row.ui-state-active {
   background: #F5F7D7;
 }
 
-.sr {
+.slick-row {
   position: absolute;
   background: white;
   border: 0px;
   line-height: 20px;
 }
 
-.sr.selected {
+.slick-row.selected {
   z-index: 10;
   background: #DFE8F6;
 }
 
-.sc {
+.slick-cell {
   padding-left: 4px;
   padding-right: 4px;
 }
@@ -73,11 +73,11 @@ classes should alter those!
   background: white;
 }
 
-.sc.selected {
+.slick-cell.selected {
   background-color: beige;
 }
 
-.sc.active {
+.slick-cell.active {
   border-color: gray;
   border-style: solid;
 }
@@ -86,20 +86,20 @@ classes should alter those!
   background: silver !important;
 }
 
-.sr.odd {
+.slick-row.odd {
   background: #fafafa;
 }
 
-.sr.ui-state-active {
+.slick-row.ui-state-active {
   background: #F5F7D7;
 }
 
-.sr.loading {
+.slick-row.loading {
   opacity: 0.5;
   filter: alpha(opacity = 50);
 }
 
-.sc.invalid {
+.slick-cell.invalid {
   border-color: red;
   -moz-animation-duration: 0.2s;
   -webkit-animation-duration: 0.2s;
diff --git a/web/pgadmin/static/vendor/slickgrid/slick.core.js b/web/pgadmin/static/vendor/slickgrid/slick.core.js
index c4f04ec4..1846a50a 100644
--- a/web/pgadmin/static/vendor/slickgrid/slick.core.js
+++ b/web/pgadmin/static/vendor/slickgrid/slick.core.js
@@ -40,7 +40,8 @@
         RIGHT: 39,
         TAB: 9,
         UP: 38
-      }
+      },
+      "preClickClassName" : "slick-edit-preclick"
     }
   });
 
@@ -423,7 +424,7 @@
 
     /***
      * Sets the specified edit controller as the active edit controller (acquire edit lock).
-     * If another edit controller is already active, and exception will be thrown.
+     * If another edit controller is already active, and exception will be throw new Error(.
      * @method activate
      * @param editController {EditController} edit controller acquiring the lock
      */
@@ -432,26 +433,26 @@
         return;
       }
       if (activeEditController !== null) {
-        throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
+        throw new Error("SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController");
       }
       if (!editController.commitCurrentEdit) {
-        throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
+        throw new Error("SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()");
       }
       if (!editController.cancelCurrentEdit) {
-        throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
+        throw new Error("SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()");
       }
       activeEditController = editController;
     };
 
     /***
      * Unsets the specified edit controller as the active edit controller (release edit lock).
-     * If the specified edit controller is not the active one, an exception will be thrown.
+     * If the specified edit controller is not the active one, an exception will be throw new Error(.
      * @method deactivate
      * @param editController {EditController} edit controller releasing the lock
      */
     this.deactivate = function (editController) {
       if (activeEditController !== editController) {
-        throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
+        throw new Error("SlickGrid.EditorLock.deactivate: specified editController is not the currently active one");
       }
       activeEditController = null;
     };
diff --git a/web/pgadmin/static/vendor/slickgrid/slick.dataview.js b/web/pgadmin/static/vendor/slickgrid/slick.dataview.js
index f62df669..b004b800 100644
--- a/web/pgadmin/static/vendor/slickgrid/slick.dataview.js
+++ b/web/pgadmin/static/vendor/slickgrid/slick.dataview.js
@@ -107,7 +107,7 @@
       for (var i = startingIndex, l = items.length; i < l; i++) {
         id = items[i][idProperty];
         if (id === undefined) {
-          throw "Each data element must implement a unique 'id' property";
+          throw new Error("Each data element must implement a unique 'id' property");
         }
         idxById[id] = i;
       }
@@ -118,7 +118,7 @@
       for (var i = 0, l = items.length; i < l; i++) {
         id = items[i][idProperty];
         if (id === undefined || idxById[id] !== i) {
-          throw "Each data element must implement a unique 'id' property";
+          throw new Error("Each data element must implement a unique 'id' property");
         }
       }
     }
@@ -210,6 +210,15 @@
       }
     }
 
+    function getFilteredItems(){
+      return filteredItems;
+    }
+
+
+    function getFilter(){
+      return filter;
+    }
+    
     function setFilter(filterFn) {
       filter = filterFn;
       if (options.inlineFilters) {
@@ -330,7 +339,7 @@
 
     function updateItem(id, item) {
       if (idxById[id] === undefined || id !== item[idProperty]) {
-        throw "Invalid or non-matching id";
+        throw new Error("Invalid or non-matching id");
       }
       items[idxById[id]] = item;
       if (!updated) {
@@ -355,7 +364,7 @@
     function deleteItem(id) {
       var idx = idxById[id];
       if (idx === undefined) {
-        throw "Invalid id";
+        throw new Error("Invalid id");
       }
       delete idxById[id];
       items.splice(idx, 1);
@@ -762,14 +771,17 @@
       // get the current page
       var paged;
       if (pagesize) {
-        if (filteredItems.length < pagenum * pagesize) {
-          pagenum = Math.floor(filteredItems.length / pagesize);
+        if (filteredItems.length <= pagenum * pagesize) {
+          if (filteredItems.length === 0) {
+            pagenum = 0;
+          } else {
+            pagenum = Math.floor((filteredItems.length - 1) / pagesize);
+          }
         }
         paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize);
       } else {
         paged = filteredItems;
       }
-
       return {totalRows: filteredItems.length, rows: paged};
     }
 
@@ -980,6 +992,10 @@
         if (key != args.key) { return; }
         if (args.hash) {
           storeCellCssStyles(args.hash);
+        } else {
+          grid.onCellCssStylesChanged.unsubscribe(styleChanged);
+          self.onRowsChanged.unsubscribe(update);
+          self.onRowCountChanged.unsubscribe(update);          
         }
       });
 
@@ -997,6 +1013,8 @@
       "getItems": getItems,
       "setItems": setItems,
       "setFilter": setFilter,
+      "getFilter": getFilter,
+      "getFilteredItems": getFilteredItems,
       "sort": sort,
       "fastSort": fastSort,
       "reSort": reSort,
diff --git a/web/pgadmin/static/vendor/slickgrid/slick.editors.js b/web/pgadmin/static/vendor/slickgrid/slick.editors.js
index e852592c..33b4e266 100644
--- a/web/pgadmin/static/vendor/slickgrid/slick.editors.js
+++ b/web/pgadmin/static/vendor/slickgrid/slick.editors.js
@@ -11,7 +11,7 @@
       "Editors": {
         "Text": TextEditor,
         "Integer": IntegerEditor,
-		"Float": FloatEditor,
+        "Float": FloatEditor,
         "Date": DateEditor,
         "YesNoSelect": YesNoSelectEditor,
         "Checkbox": CheckboxEditor,
@@ -29,7 +29,7 @@
     this.init = function () {
       $input = $("<INPUT type=text class='editor-text' />")
           .appendTo(args.container)
-          .bind("keydown.nav", function (e) {
+          .on("keydown.nav", function (e) {
             if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
               e.stopImmediatePropagation();
             }
@@ -98,7 +98,7 @@
     this.init = function () {
       $input = $("<INPUT type=text class='editor-text' />");
 
-      $input.bind("keydown.nav", function (e) {
+      $input.on("keydown.nav", function (e) {
         if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
           e.stopImmediatePropagation();
         }
@@ -167,7 +167,7 @@
     this.init = function () {
       $input = $("<INPUT type=text class='editor-text' />");
 
-      $input.bind("keydown.nav", function (e) {
+      $input.on("keydown.nav", function (e) {
         if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
           e.stopImmediatePropagation();
         }
@@ -185,24 +185,24 @@
       $input.focus();
     };
 
-	function getDecimalPlaces() {
-		// returns the number of fixed decimal places or null
-		var rtn = args.column.editorFixedDecimalPlaces;
-		if (typeof rtn == 'undefined') {
-			rtn = FloatEditor.DefaultDecimalPlaces;
-		}
-		return (!rtn && rtn!==0 ? null : rtn);
-	}
+    function getDecimalPlaces() {
+        // returns the number of fixed decimal places or null
+        var rtn = args.column.editorFixedDecimalPlaces;
+        if (typeof rtn == 'undefined') {
+            rtn = FloatEditor.DefaultDecimalPlaces;
+        }
+        return (!rtn && rtn!==0 ? null : rtn);
+    }
 
     this.loadValue = function (item) {
       defaultValue = item[args.column.field];
 
-	  var decPlaces = getDecimalPlaces();
-	  if (decPlaces !== null
-	  && (defaultValue || defaultValue===0)
-	  && defaultValue.toFixed) {
-		defaultValue = defaultValue.toFixed(decPlaces);
-	  }
+      var decPlaces = getDecimalPlaces();
+      if (decPlaces !== null
+      && (defaultValue || defaultValue===0)
+      && defaultValue.toFixed) {
+        defaultValue = defaultValue.toFixed(decPlaces);
+      }
 
       $input.val(defaultValue);
       $input[0].defaultValue = defaultValue;
@@ -210,14 +210,14 @@
     };
 
     this.serializeValue = function () {
-	  var rtn = parseFloat($input.val()) || 0;
+      var rtn = parseFloat($input.val()) || 0;
 
-	  var decPlaces = getDecimalPlaces();
-	  if (decPlaces !== null
-	  && (rtn || rtn===0)
-	  && rtn.toFixed) {
-		rtn = parseFloat(rtn.toFixed(decPlaces));
-	  }
+      var decPlaces = getDecimalPlaces();
+      if (decPlaces !== null
+      && (rtn || rtn===0)
+      && rtn.toFixed) {
+        rtn = parseFloat(rtn.toFixed(decPlaces));
+      }
 
       return rtn;
     };
@@ -269,8 +269,7 @@
       $input.datepicker({
         showOn: "button",
         buttonImageOnly: true,
-        buttonImage: "../images/calendar.gif",
-        beforeShow: function () {
+         beforeShow: function () {
           calendarOpen = true
         },
         onClose: function () {
@@ -422,6 +421,10 @@
       }
     };
 
+    this.preClick = function () {
+        $select.prop('checked', !$select.prop('checked'));
+    };
+
     this.serializeValue = function () {
       return $select.prop('checked');
     };
@@ -470,7 +473,7 @@
         }
       });
 
-      $picker.find(".editor-percentcomplete-buttons button").bind("click", function (e) {
+      $picker.find(".editor-percentcomplete-buttons button").on("click", function (e) {
         $input.val($(this).attr("val"));
         $picker.find(".editor-percentcomplete-slider").slider("value", $(this).attr("val"));
       })
@@ -535,15 +538,15 @@
       $wrapper = $("<DIV style='z-index:10000;position:absolute;background:white;padding:5px;border:3px solid gray; -moz-border-radius:10px; border-radius:10px;'/>")
           .appendTo($container);
 
-      $input = $("<TEXTAREA hidefocus rows=5 style='backround:white;width:250px;height:80px;border:0;outline:0'>")
+      $input = $("<TEXTAREA hidefocus rows=5 style='background:white;width:250px;height:80px;border:0;outline:0'>")
           .appendTo($wrapper);
 
       $("<DIV style='text-align:right'><BUTTON>Save</BUTTON><BUTTON>Cancel</BUTTON></DIV>")
           .appendTo($wrapper);
 
-      $wrapper.find("button:first").bind("click", this.save);
-      $wrapper.find("button:last").bind("click", this.cancel);
-      $input.bind("keydown", this.handleKeyDown);
+      $wrapper.find("button:first").on("click", this.save);
+      $wrapper.find("button:last").on("click", this.cancel);
+      $input.on("keydown", this.handleKeyDown);
 
       scope.position(args.position);
       $input.focus().select();
diff --git a/web/pgadmin/static/vendor/slickgrid/slick.formatters.js b/web/pgadmin/static/vendor/slickgrid/slick.formatters.js
index 302b6f31..62bb8870 100644
--- a/web/pgadmin/static/vendor/slickgrid/slick.formatters.js
+++ b/web/pgadmin/static/vendor/slickgrid/slick.formatters.js
@@ -16,7 +16,9 @@
         "PercentComplete": PercentCompleteFormatter,
         "PercentCompleteBar": PercentCompleteBarFormatter,
         "YesNo": YesNoFormatter,
-        "Checkmark": CheckmarkFormatter
+        "Checkmark": CheckmarkFormatter,
+        "Checkbox": CheckboxFormatter
+
       }
     }
   });
@@ -53,6 +55,10 @@
     return value ? "Yes" : "No";
   }
 
+  function CheckboxFormatter(row, cell, value, columnDef, dataContext) {
+    return '<img class="slick-edit-preclick" src="../images/' + (value ? "CheckboxY" : "CheckboxN") + '.png">';
+  }
+
   function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
     return value ? "<img src='../images/tick.png'>" : "";
   }
diff --git a/web/pgadmin/static/vendor/slickgrid/slick.grid.css b/web/pgadmin/static/vendor/slickgrid/slick.grid.css
index a628c029..ce085eff 100644
--- a/web/pgadmin/static/vendor/slickgrid/slick.grid.css
+++ b/web/pgadmin/static/vendor/slickgrid/slick.grid.css
@@ -5,13 +5,13 @@ No built-in (selected, editable, highlight, flashing, invalid, loading, :focus)
 classes should alter those!
 */
 
-.slick-header.ui-state-default, .slick-headerrow.ui-state-default {
+.slick-header.ui-state-default, .slick-headerrow.ui-state-default, .slick-footerrow.ui-state-default  {
   width: 100%;
   overflow: hidden;
   border-left: 0px !important;
 }
 
-.slick-header-columns, .slick-headerrow-columns {
+.slick-header-columns, .slick-headerrow-columns, .slick-footerrow-columns {
   position: relative;
   white-space: nowrap;
   cursor: default;
@@ -21,7 +21,7 @@ classes should alter those!
 .slick-header-column.ui-state-default {
   position: relative;
   display: inline-block;
-  /*box-sizing: content-box !important; */
+ /*box-sizing: content-box !important; use this for Firefox! */ 
   overflow: hidden;
   -o-text-overflow: ellipsis;
   text-overflow: ellipsis;
@@ -36,7 +36,7 @@ classes should alter those!
   float: left;
 }
 
-.slick-headerrow-column.ui-state-default {
+.slick-headerrow-column.ui-state-default, .slick-footerrow-column.ui-state-default {
   padding: 4px;
 }
 
@@ -53,6 +53,21 @@ classes should alter those!
   float: left;
 }
 
+.slick-sort-indicator-numbered {
+  display: inline-block;
+  width: 8px;
+  height: 5px;
+  margin-left: 4px;
+  margin-top: 0;
+  padding-left: 1px;
+  line-height: 20px;
+  float: left;
+  font-family: Arial;
+  font-style: normal;
+  font-weight: bold;
+  color: #6190CD;
+}
+
 .slick-sort-indicator-desc {
   background: url(images/sort-desc.gif);
 }
@@ -81,13 +96,13 @@ classes should alter those!
   outline: 0;
 }
 
-.sr.ui-widget-content, .sr.ui-state-active {
+.slick-row.ui-widget-content, .slick-row.ui-state-active {
   position: absolute;
   border: 0px;
   width: 100%;
 }
 
-.sc, .slick-headerrow-column {
+.slick-cell, .slick-headerrow-column , .slick-footerrow-column{
   position: absolute;
   border: 1px solid transparent;
   border-right: 1px dotted silver;
@@ -102,6 +117,12 @@ classes should alter those!
   white-space: nowrap;
   cursor: default;
 }
+.slick-cell, .slick-headerrow-column{
+  border-bottom-color: silver;
+}
+.slick-footerrow-column {
+  border-top-color: silver;
+}
  
 .slick-group {
 }
@@ -110,7 +131,7 @@ classes should alter those!
   display: inline-block;
 }
 
-.sc.highlighted {
+.slick-cell.highlighted {
   background: lightskyblue;
   background: rgba(0, 0, 255, 0.2);
   -webkit-transition: all 0.5s;
@@ -119,11 +140,11 @@ classes should alter those!
   transition: all 0.5s;
 }
 
-.sc.flashing {
+.slick-cell.flashing {
   border: 1px solid red !important;
 }
 
-.sc.editable {
+.slick-cell.editable {
   z-index: 11;
   overflow: visible;
   background: white;
@@ -131,7 +152,7 @@ classes should alter those!
   border-style: solid;
 }
 
-.sc:focus {
+.slick-cell:focus {
   outline: none;
 }
 
diff --git a/web/pgadmin/static/vendor/slickgrid/slick.grid.js b/web/pgadmin/static/vendor/slickgrid/slick.grid.js
index 29d07e4f..8ebfd68b 100644
--- a/web/pgadmin/static/vendor/slickgrid/slick.grid.js
+++ b/web/pgadmin/static/vendor/slickgrid/slick.grid.js
@@ -1,13 +1,13 @@
 /**
  * @license
- * (c) 2009-2013 Michael Leibman
+ * (c) 2009-2016 Michael Leibman
  * michael{dot}leibman{at}gmail{dot}com
  * http://github.com/mleibman/slickgrid
  *
  * Distributed under MIT license.
  * All rights reserved.
  *
- * SlickGrid v2.2
+ * SlickGrid v2.3
  *
  * NOTES:
  *     Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods.
@@ -18,13 +18,13 @@
 
 // make sure required JavaScript modules are loaded
 if (typeof jQuery === "undefined") {
-  throw "SlickGrid requires jquery module to be loaded";
+  throw new Error("SlickGrid requires jquery module to be loaded");
 }
 if (!jQuery.fn.drag) {
-  throw "SlickGrid requires jquery.event.drag module to be loaded";
+  throw new Error("SlickGrid requires jquery.event.drag module to be loaded");
 }
 if (typeof Slick === "undefined") {
-  throw "slick.core.js not loaded";
+  throw new Error("slick.core.js not loaded");
 }
 
 
@@ -75,6 +75,9 @@ if (typeof Slick === "undefined") {
       editorLock: Slick.GlobalEditorLock,
       showHeaderRow: false,
       headerRowHeight: 25,
+      createFooterRow: false,
+      showFooterRow: false,
+      footerRowHeight: 25,
       showTopPanel: false,
       topPanelHeight: 25,
       formatterFactory: null,
@@ -86,9 +89,12 @@ if (typeof Slick === "undefined") {
       dataItemColumnValueExtractor: null,
       fullWidthRows: false,
       multiColumnSort: false,
+      numberedMultiColumnSort: false,
+      tristateMultiColumnSort: false,
       defaultFormatter: defaultFormatter,
       forceSyncScrolling: false,
-      addNewRowCssClass: "new-row"
+      addNewRowCssClass: "new-row",
+      preserveCopiedSelectionOnPaste: false
     };
 
     var columnDefaults = {
@@ -123,6 +129,7 @@ if (typeof Slick === "undefined") {
     var $headerScroller;
     var $headers;
     var $headerRow, $headerRowScroller, $headerRowSpacer;
+    var $footerRow, $footerRowScroller, $footerRowSpacer;
     var $topPanelScroller;
     var $topPanel;
     var $viewport;
@@ -136,6 +143,7 @@ if (typeof Slick === "undefined") {
     var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding
         cellWidthDiff = 0, cellHeightDiff = 0, jQueryNewWidthBehaviour = false;
     var absoluteColumnMinWidth;
+    var sortIndicatorCssClass = "slick-sort-indicator";
     
     var tabbingDirection = 1;
     var activePosX;
@@ -166,6 +174,8 @@ if (typeof Slick === "undefined") {
     var columnPosLeft = [];
     var columnPosRight = [];
 
+    var pagingActive = false;
+    var pagingIsLastPage = false;
     
     // async call handles
     var h_editorLoader = null;
@@ -198,7 +208,11 @@ if (typeof Slick === "undefined") {
     // Initialization
 
     function init() {
-      $container = $(container);
+      if (container instanceof jQuery) { 
+        $container = container; 
+      } else { 
+        $container = $(container); 
+      }
       if ($container.length < 1) {
         throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM.");
       }
@@ -275,6 +289,20 @@ if (typeof Slick === "undefined") {
 
       $canvas = $("<div class='grid-canvas' />").appendTo($viewport);
 
+      if (options.createFooterRow) {
+        $footerRowScroller = $("<div class='slick-footerrow ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
+        $footerRow = $("<div class='slick-footerrow-columns' />").appendTo($footerRowScroller);
+        $footerRowSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
+            .css("width", getCanvasWidth() + scrollbarDimensions.width + "px")
+            .appendTo($footerRowScroller);
+
+        if (!options.showFooterRow) {
+          $footerRowScroller.hide();
+        }
+      }
+
+      if (options.numberedMultiColumnSort) { sortIndicatorCssClass = "slick-sort-indicator-numbered"; }
+
       $focusSink2 = $focusSink.clone().appendTo($container);
 
       if (!options.explicitInitialization) {
@@ -302,7 +330,7 @@ if (typeof Slick === "undefined") {
         if (!options.enableTextSelectionOnCells) {
           // disable text selection in grid cells except in input and textarea elements
           // (this is IE-specific, because selectstart event will only fire in IE)
-          $viewport.bind("selectstart.ui", function (event) {
+          $viewport.on("selectstart.ui", function (event) {
             return $(event.target).is("input,textarea");
           });
         }
@@ -315,35 +343,41 @@ if (typeof Slick === "undefined") {
         bindAncestorScrollEvents();
 
         $container
-            .bind("resize.slickgrid", resizeCanvas);
+            .on("resize.slickgrid", resizeCanvas);
         $viewport
-            //.bind("click", handleClick)
-            .bind("scroll", handleScroll);
+            //.on("click", handleClick)
+            .on("scroll", handleScroll);
         $headerScroller
-            .bind("contextmenu", handleHeaderContextMenu)
-            .bind("click", handleHeaderClick)
-            .delegate(".slick-header-column", "mouseenter", handleHeaderMouseEnter)
-            .delegate(".slick-header-column", "mouseleave", handleHeaderMouseLeave);
+            .on("contextmenu", handleHeaderContextMenu)
+            .on("click", handleHeaderClick)
+            .on("mouseenter", ".slick-header-column", handleHeaderMouseEnter)
+            .on("mouseleave", ".slick-header-column", handleHeaderMouseLeave);
         $headerRowScroller
-            .bind("scroll", handleHeaderRowScroll);
+            .on("scroll", handleHeaderRowScroll);
+
+        if (options.createFooterRow) {
+          $footerRowScroller
+              .on("scroll", handleFooterRowScroll);
+        }
+
         $focusSink.add($focusSink2)
-            .bind("keydown", handleKeyDown);
+            .on("keydown", handleKeyDown);
         $canvas
-            .bind("keydown", handleKeyDown)
-            .bind("click", handleClick)
-            .bind("dblclick", handleDblClick)
-            .bind("contextmenu", handleContextMenu)
-            .bind("draginit", handleDragInit)
-            .bind("dragstart", {distance: 3}, handleDragStart)
-            .bind("drag", handleDrag)
-            .bind("dragend", handleDragEnd)
-            .delegate(".sc", "mouseenter", handleMouseEnter)
-            .delegate(".sc", "mouseleave", handleMouseLeave);
+            .on("keydown", handleKeyDown)
+            .on("click", handleClick)
+            .on("dblclick", handleDblClick)
+            .on("contextmenu", handleContextMenu)
+            .on("draginit", handleDragInit)
+            .on("dragstart", {distance: 3}, handleDragStart)
+            .on("drag", handleDrag)
+            .on("dragend", handleDragEnd)
+            .on("mouseenter", ".slick-cell", handleMouseEnter)
+            .on("mouseleave", ".slick-cell", handleMouseLeave);
 
         // Work around http://crbug.com/312427.
         if (navigator.userAgent.toLowerCase().match(/webkit/) &&
             navigator.userAgent.toLowerCase().match(/macintosh/)) {
-          $canvas.bind("mousewheel", handleMouseWheel);
+          $canvas.on("mousewheel", handleMouseWheel);
         }
         restoreCssFromHiddenInit();
       }
@@ -351,7 +385,7 @@ if (typeof Slick === "undefined") {
 
     function cacheCssForHiddenInit() {
       // handle display:none on container or container parents
-      $hiddenParents = $container.parents().andSelf().not(':visible');
+      $hiddenParents = $container.parents().addBack().not(':visible');
       $hiddenParents.each(function() {
         var old = {};
         for ( var name in cssShow ) {
@@ -423,13 +457,20 @@ if (typeof Slick === "undefined") {
       return dim;
     }
 
-    function getHeadersWidth() {
-      var headersWidth = 0;
+    function getColumnTotalWidth(includeScrollbar) {
+      var totalWidth = 0;
       for (var i = 0, ii = columns.length; i < ii; i++) {
         var width = columns[i].width;
-        headersWidth += width;
+        totalWidth += width;
+      }
+      if (includeScrollbar) {
+        totalWidth += scrollbarDimensions.width;
       }
-      headersWidth += scrollbarDimensions.width;
+      return totalWidth;
+    }
+
+    function getHeadersWidth() {
+      var headersWidth = getColumnTotalWidth(true);
       return Math.max(headersWidth, viewportW) + 1000;
     }
 
@@ -450,11 +491,14 @@ if (typeof Slick === "undefined") {
       if (canvasWidth != oldCanvasWidth) {
         $canvas.width(canvasWidth);
         $headerRow.width(canvasWidth);
+        if (options.createFooterRow) { $footerRow.width(canvasWidth); }
         $headers.width(getHeadersWidth());
         viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width);
       }
 
-      $headerRowSpacer.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0));
+       var w=canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0);
+       $headerRowSpacer.width(w);
+       if (options.createFooterRow) { $footerRowSpacer.width(w); }
 
       if (canvasWidth != oldCanvasWidth || forceColumnWidthsUpdate) {
         applyColumnWidths();
@@ -466,7 +510,7 @@ if (typeof Slick === "undefined") {
         $target
             .attr("unselectable", "on")
             .css("MozUserSelect", "none")
-            .bind("selectstart.ui", function () {
+            .on("selectstart.ui", function () {
               return false;
             }); // from jquery:ui.core.js 1.7.2
       }
@@ -504,7 +548,7 @@ if (typeof Slick === "undefined") {
           } else {
             $boundAncestors = $boundAncestors.add($elem);
           }
-          $elem.bind("scroll." + uid, handleActiveCellPositionChange);
+          $elem.on("scroll." + uid, handleActiveCellPositionChange);
         }
       }
     }
@@ -513,7 +557,7 @@ if (typeof Slick === "undefined") {
       if (!$boundAncestors) {
         return;
       }
-      $boundAncestors.unbind("scroll." + uid);
+      $boundAncestors.off("scroll." + uid);
       $boundAncestors = null;
     }
 
@@ -556,12 +600,22 @@ if (typeof Slick === "undefined") {
       return $headerRow[0];
     }
 
+    function getFooterRow() {
+      return $footerRow[0];
+    }
+
     function getHeaderRowColumn(columnId) {
       var idx = getColumnIndex(columnId);
       var $header = $headerRow.children().eq(idx);
       return $header && $header[0];
     }
 
+    function getFooterRowColumn(columnId) {
+      var idx = getColumnIndex(columnId);
+      var $footer = $footerRow.children().eq(idx);
+      return $footer && $footer[0];
+    }
+
     function createColumnHeaders() {
       function onMouseEnter() {
         $(this).addClass("ui-state-hover");
@@ -598,6 +652,20 @@ if (typeof Slick === "undefined") {
         });
       $headerRow.empty();
 
+      if (options.createFooterRow) {
+        $footerRow.find(".slick-footerrow-column")
+          .each(function() {
+            var columnDef = $(this).data("column");
+            if (columnDef) {
+              trigger(self.onBeforeFooterRowCellDestroy, {
+                "node": this,
+                "column": columnDef
+              });
+            }
+          });
+        $footerRow.empty();
+      }
+
       for (var i = 0; i < columns.length; i++) {
         var m = columns[i];
 
@@ -618,7 +686,7 @@ if (typeof Slick === "undefined") {
 
         if (m.sortable) {
           header.addClass("slick-header-sortable");
-          header.append("<span class='slick-sort-indicator' />");
+          header.append("<span class='" + sortIndicatorCssClass + "' />");
         }
 
         trigger(self.onHeaderCellRendered, {
@@ -638,12 +706,26 @@ if (typeof Slick === "undefined") {
             "grid": self
           });
         }
+        if (options.createFooterRow && options.showFooterRow) {
+          var footerRowCell = $("<div class='ui-state-default slick-footerrow-column l" + i   + " r" + i + "'></div>")
+              .data("column", m)
+              .appendTo($footerRow);
+
+          trigger(self.onFooterRowCellRendered, {
+            "node": footerRowCell[0],
+            "column": m
+          });
+        }
       }
 
       setSortColumns(sortColumns);
       setupColumnResize();
       if (options.enableColumnReorder) {
-        setupColumnReorder();
+        if (typeof options.enableColumnReorder == 'function') {
+            options.enableColumnReorder(self, $headers, headerColumnWidthDiff, setColumns, setupColumnResize, columns, getColumnIndex, uid, trigger);
+        } else {
+            setupColumnReorder();
+        }
       }
     }
     
@@ -667,49 +749,68 @@ if (typeof Slick === "undefined") {
             return;
           }
 
-          var sortOpts = null;
+          var sortColumn = null;
           var i = 0;
           for (; i < sortColumns.length; i++) {
             if (sortColumns[i].columnId == column.id) {
-              sortOpts = sortColumns[i];
-              sortOpts.sortAsc = !sortOpts.sortAsc;
+              sortColumn = sortColumns[i];
+              sortColumn.sortAsc = !sortColumn.sortAsc;
               break;
             }
           }
+          var hadSortCol = !!sortColumn;
           
-          if (e.metaKey && options.multiColumnSort) {
-            if (sortOpts) {
-              sortColumns.splice(i, 1);
-            }
-          }
-          else {
-            if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {
-              sortColumns = [];
-            }
+          if (options.tristateMultiColumnSort) {
+              if (!sortColumn) { 
+                sortColumn = { columnId: column.id, sortAsc: column.defaultSortAsc }; 
+              }
+              if (hadSortCol && sortColumn.sortAsc) {
+                // three state: remove sort rather than go back to ASC
+                sortColumns.splice(i, 1);
+                sortColumn = null;
+              }
+              if (!options.multiColumnSort) { sortColumns = []; }
+              if (sortColumn && (!hadSortCol || !options.multiColumnSort)) { 
+                sortColumns.push(sortColumn); 
+              }
+          } else {
+              // legacy behaviour
+              if (e.metaKey && options.multiColumnSort) {
+                if (sortColumn) {
+                  sortColumns.splice(i, 1);
+                }
+              }
+              else {
+                if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {
+                  sortColumns = [];
+                }
 
-            if (!sortOpts) {
-              sortOpts = { columnId: column.id, sortAsc: column.defaultSortAsc };
-              sortColumns.push(sortOpts);
-            } else if (sortColumns.length == 0) {
-              sortColumns.push(sortOpts);
-            }
+                if (!sortColumn) {
+                  sortColumn = { columnId: column.id, sortAsc: column.defaultSortAsc };
+                  sortColumns.push(sortColumn);
+                } else if (sortColumns.length == 0) {
+                  sortColumns.push(sortColumn);
+                }
+              }              
           }
           
           setSortColumns(sortColumns);
 
-          if (!options.multiColumnSort) {
-            trigger(self.onSort, {
-              multiColumnSort: false,
-              sortCol: column,
-              sortAsc: sortOpts.sortAsc,
-              grid: self}, e);
-          } else {
-            trigger(self.onSort, {
-              multiColumnSort: true,
-              sortCols: $.map(sortColumns, function(col) {
-                return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc };
-              }),
-              grid: self}, e);
+          if (sortColumns.length > 0) { 
+              if (!options.multiColumnSort) {
+                trigger(self.onSort, {
+                  multiColumnSort: false,
+                  sortCol: column,
+                  sortAsc: sortColumns[0].sortAsc,
+                  grid: self}, e);
+              } else {
+                trigger(self.onSort, {
+                  multiColumnSort: true,
+                  sortCols: $.map(sortColumns, function(col) {
+                    return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc };
+                  }),
+                  grid: self}, e);
+              }
           }
         }
       });
@@ -757,6 +858,7 @@ if (typeof Slick === "undefined") {
       columnElements = $headers.children();
       columnElements.find(".slick-resizable-handle").remove();
       columnElements.each(function (i, e) {
+        if (i >= columns.length) { return; }
         if (columns[i].resizable) {
           if (firstResizable === undefined) {
             firstResizable = i;
@@ -768,13 +870,14 @@ if (typeof Slick === "undefined") {
         return;
       }
       columnElements.each(function (i, e) {
+        if (i >= columns.length) { return; }
         if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) {
           return;
         }
         $col = $(e);
         $("<div class='slick-resizable-handle' />")
             .appendTo(e)
-            .bind("dragstart", function (e, dd) {
+            .on("dragstart", function (e, dd) {
               if (!getEditorLock().commitCurrentEdit()) {
                 return false;
               }
@@ -783,13 +886,14 @@ if (typeof Slick === "undefined") {
               var shrinkLeewayOnRight = null, stretchLeewayOnRight = null;
               // lock each column's width option to current width
               columnElements.each(function (i, e) {
+                if (i >= columns.length) { return; }
                 columns[i].previousWidth = $(e).outerWidth();
               });
               if (options.forceFitColumns) {
                 shrinkLeewayOnRight = 0;
                 stretchLeewayOnRight = 0;
                 // colums on right affect maxPageX/minPageX
-                for (j = i + 1; j < columnElements.length; j++) {
+                for (j = i + 1; j < columns.length; j++) {
                   c = columns[j];
                   if (c.resizable) {
                     if (stretchLeewayOnRight !== null) {
@@ -833,7 +937,7 @@ if (typeof Slick === "undefined") {
               maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft);
               minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight);
             })
-            .bind("drag", function (e, dd) {
+            .on("drag", function (e, dd) {
               var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x;
               if (d < 0) { // shrink column
                 x = d;
@@ -853,7 +957,7 @@ if (typeof Slick === "undefined") {
 
                 if (options.forceFitColumns) {
                   x = -d;
-                  for (j = i + 1; j < columnElements.length; j++) {
+                  for (j = i + 1; j < columns.length; j++) {
                     c = columns[j];
                     if (c.resizable) {
                       if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
@@ -883,7 +987,7 @@ if (typeof Slick === "undefined") {
 
                 if (options.forceFitColumns) {
                   x = -d;
-                  for (j = i + 1; j < columnElements.length; j++) {
+                  for (j = i + 1; j < columns.length; j++) {
                     c = columns[j];
                     if (c.resizable) {
                       actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
@@ -903,10 +1007,10 @@ if (typeof Slick === "undefined") {
                 applyColumnWidths();
               }
             })
-            .bind("dragend", function (e, dd) {
+            .on("dragend", function (e, dd) {
               var newWidth;
               $(this).parent().removeClass("slick-header-column-active");
-              for (j = 0; j < columnElements.length; j++) {
+              for (j = 0; j < columns.length; j++) {
                 c = columns[j];
                 newWidth = $(columnElements[j]).outerWidth();
 
@@ -935,11 +1039,11 @@ if (typeof Slick === "undefined") {
       var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"];
       var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
 
-	  // jquery prior to version 1.8 handles .width setter/getter as a direct css write/read
-	  // jquery 1.8 changed .width to read the true inner element width if box-sizing is set to border-box, and introduced a setter for .outerWidth
-	  // so for equivalent functionality, prior to 1.8 use .width, and after use .outerWidth
-	  var verArray = $.fn.jquery.split('.');
-	  jQueryNewWidthBehaviour = (verArray[0]==1 && verArray[1]>=8) ||  verArray[0] >=2;
+      // jquery prior to version 1.8 handles .width setter/getter as a direct css write/read
+      // jquery 1.8 changed .width to read the true inner element width if box-sizing is set to border-box, and introduced a setter for .outerWidth
+      // so for equivalent functionality, prior to 1.8 use .width, and after use .outerWidth
+      var verArray = $.fn.jquery.split('.');
+      jQueryNewWidthBehaviour = (verArray[0]==1 && verArray[1]>=8) ||  verArray[0] >=2;
 
       el = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers);
       headerColumnWidthDiff = headerColumnHeightDiff = 0;
@@ -953,8 +1057,8 @@ if (typeof Slick === "undefined") {
       }
       el.remove();
 
-      var r = $("<div class='sr' />").appendTo($canvas);
-      el = $("<div class='sc' id='' style='visibility:hidden'>-</div>").appendTo(r);
+      var r = $("<div class='slick-row' />").appendTo($canvas);
+      el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r);
       cellWidthDiff = cellHeightDiff = 0;
       if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") {
         $.each(h, function (n, val) {
@@ -976,8 +1080,9 @@ if (typeof Slick === "undefined") {
         "." + uid + " .slick-header-column { left: 1000px; }",
         "." + uid + " .slick-top-panel { height:" + options.topPanelHeight + "px; }",
         "." + uid + " .slick-headerrow-columns { height:" + options.headerRowHeight + "px; }",
-        "." + uid + " .sc { height:" + rowHeight + "px; }",
-        "." + uid + " .sr { height:" + options.rowHeight + "px; }"
+        "." + uid + " .slick-footerrow-columns { height:" + options.footerRowHeight + "px; }",
+        "." + uid + " .slick-cell { height:" + rowHeight + "px; }",
+        "." + uid + " .slick-row { height:" + options.rowHeight + "px; }"
       ];
 
       for (var i = 0; i < columns.length; i++) {
@@ -993,9 +1098,10 @@ if (typeof Slick === "undefined") {
     }
 
     function getColumnCssRules(idx) {
+      var i;
       if (!stylesheet) {
         var sheets = document.styleSheets;
-        for (var i = 0; i < sheets.length; i++) {
+        for (i = 0; i < sheets.length; i++) {
           if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
             stylesheet = sheets[i];
             break;
@@ -1011,7 +1117,7 @@ if (typeof Slick === "undefined") {
         columnCssRulesR = [];
         var cssRules = (stylesheet.cssRules || stylesheet.rules);
         var matches, columnIdx;
-        for (var i = 0; i < cssRules.length; i++) {
+        for (i = 0; i < cssRules.length; i++) {
           var selector = cssRules[i].selectorText;
           if (matches = /\.l\d+/.exec(selector)) {
             columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
@@ -1049,10 +1155,10 @@ if (typeof Slick === "undefined") {
       }
 
       unbindAncestorScrollEvents();
-      $container.unbind(".slickgrid");
+      $container.off(".slickgrid");
       removeCssRules();
 
-      $canvas.unbind("draginit dragstart dragend drag");
+      $canvas.off("draginit dragstart dragend drag");
       $container.empty().removeClass(uid);
     }
 
@@ -1161,17 +1267,18 @@ if (typeof Slick === "undefined") {
     function applyColumnHeaderWidths() {
       if (!initialized) { return; }
       var h;
-      for (var i = 0, headers = $headers.children(), ii = headers.length; i < ii; i++) {
+
+      for (var i = 0, headers = $headers.children(), ii = columns.length; i < ii; i++) {
         h = $(headers[i]);
-		if (jQueryNewWidthBehaviour) {
-			if (h.outerWidth() !== columns[i].width) {
-			  h.outerWidth(columns[i].width);
-			}
-		} else {
-			if (h.width() !== columns[i].width - headerColumnWidthDiff) {
-			  h.width(columns[i].width - headerColumnWidthDiff);
-			}
-		}
+        if (jQueryNewWidthBehaviour) {
+            if (h.outerWidth() !== columns[i].width) {
+              h.outerWidth(columns[i].width);
+            }
+        } else {
+            if (h.width() !== columns[i].width - headerColumnWidthDiff) {
+              h.width(columns[i].width - headerColumnWidthDiff);
+            }
+        }
       }
 
       updateColumnCaches();
@@ -1196,12 +1303,13 @@ if (typeof Slick === "undefined") {
 
     function setSortColumns(cols) {
       sortColumns = cols;
-
+      var numberCols = options.numberedMultiColumnSort && sortColumns.length > 1;
       var headerColumnEls = $headers.children();
-      headerColumnEls
+      var sortIndicatorEl = headerColumnEls
           .removeClass("slick-header-column-sorted")
-          .find(".slick-sort-indicator")
+          .find("." + sortIndicatorCssClass)
               .removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");
+      if (numberCols) { sortIndicatorEl.text(''); }
 
       $.each(sortColumns, function(i, col) {
         if (col.sortAsc == null) {
@@ -1209,10 +1317,11 @@ if (typeof Slick === "undefined") {
         }
         var columnIndex = getColumnIndex(col.columnId);
         if (columnIndex != null) {
-          headerColumnEls.eq(columnIndex)
+          sortIndicatorEl = headerColumnEls.eq(columnIndex)
               .addClass("slick-header-column-sorted")
-              .find(".slick-sort-indicator")
+              .find("." + sortIndicatorCssClass)
                   .addClass(col.sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");
+          if (numberCols) { sortIndicatorEl.text(i+1); }
         }
       });
     }
@@ -1291,7 +1400,7 @@ if (typeof Slick === "undefined") {
       return options;
     }
 
-    function setOptions(args) {
+    function setOptions(args, suppressRender) {
       if (!getEditorLock().commitCurrentEdit()) {
         return;
       }
@@ -1306,7 +1415,7 @@ if (typeof Slick === "undefined") {
       validateAndEnforceOptions();
 
       $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
-      render();
+      if (!suppressRender) { render(); }
     }
 
     function validateAndEnforceOptions() {
@@ -1337,7 +1446,9 @@ if (typeof Slick === "undefined") {
     }
 
     function getDataLengthIncludingAddNew() {
-      return getDataLength() + (options.enableAddRow ? 2 : 1);
+      return getDataLength() + (!options.enableAddRow ? 0
+        : (!pagingActive || pagingIsLastPage ? 1 : 0)
+      );
     }
 
     function getDataItem(i) {
@@ -1374,6 +1485,17 @@ if (typeof Slick === "undefined") {
       }
     }
 
+    function setFooterRowVisibility(visible) {
+      if (options.showFooterRow != visible) {
+        options.showFooterRow = visible;
+        if (visible) {
+          $footerRowScroller.slideDown("fast", resizeCanvas);
+        } else {
+          $footerRowScroller.slideUp("fast", resizeCanvas);
+        }
+      }
+    }
+
     function getContainerNode() {
       return $container.get(0);
     }
@@ -1461,13 +1583,12 @@ if (typeof Slick === "undefined") {
     function appendRowHtml(stringArray, row, range, dataLength) {
       var d = getDataItem(row);
       var dataLoading = row < dataLength && !d;
-      var diff = dataLength - row;
-      var rowCss = "sr" +
+      var rowCss = "slick-row" +
           (dataLoading ? " loading" : "") +
           (row === activeRow ? " active" : "") +
-          (((options.enableAddRow && diff < 0) || diff == 0) ? "" : (row % 2 == 1 ? " odd" : " even"));
+          (row % 2 == 1 ? " odd" : " even");
 
-      if (!d && options.enableAddRow && diff == 0) {
+      if (!d) {
         rowCss += " " + options.addNewRowCssClass;
       }
 
@@ -1479,11 +1600,6 @@ if (typeof Slick === "undefined") {
 
       stringArray.push("<div class='ui-widget-content " + rowCss + "' style='top:" + getRowTop(row) + "px'>");
 
-      if (options.enableAddRow ? diff < 0 : diff == 0) {
-        stringArray.push("</div>");
-        return;
-      }
-
       var colspan, m;
       for (var i = 0, ii = columns.length; i < ii; i++) {
         m = columns[i];
@@ -1521,18 +1637,12 @@ if (typeof Slick === "undefined") {
       // item: grid data for row
 
       var m = columns[cell];
-      var cellCss = "sc l" + cell + " r" + Math.min(columns.length - 1, cell + colspan - 1) +
+      var cellCss = "slick-cell l" + cell + " r" + Math.min(columns.length - 1, cell + colspan - 1) +
           (m.cssClass ? " " + m.cssClass : "");
       if (row === activeRow && cell === activeCell) {
         cellCss += (" active");
       }
 
-      if (!item) {
-          var diff = getDataLength() - row;
-          if (options.enableAddRow ? diff < 0 : diff == 0)
-            return;
-      }
-
       // TODO:  merge them together in the setter
       for (var key in cellCssClasses) {
         if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) {
@@ -1540,12 +1650,19 @@ if (typeof Slick === "undefined") {
         }
       }
 
-      stringArray.push("<div class='" + cellCss + "'>");
+      var value = null;
+      if (item) { value = getDataItemValueForColumn(item, m); }
+      var formatterResult =  getFormatter(row, m)(row, cell, value, m, item);
+      
+      // get addl css class names from object type formatter return and from string type return of onBeforeAppendCell
+      var addlCssClasses = trigger(self.onBeforeAppendCell, { row: row, cell: cell, grid: self, value: value, dataContext: item }) || '';
+      addlCssClasses += (formatterResult.addClasses ? (addlCssClasses ? ' ' : '') + formatterResult.addClasses : '');
+      
+      stringArray.push("<div class='" + cellCss + (addlCssClasses ? ' ' + addlCssClasses : '') + "'>");
 
       // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
       if (item) {
-        var value = getDataItemValueForColumn(item, m);
-        stringArray.push(getFormatter(row, m)(row, cell, value, m, item));
+        stringArray.push(typeof formatterResult === 'string' ? formatterResult : formatterResult.text);
       }
       
       stringArray.push("</div>");
@@ -1646,7 +1763,8 @@ if (typeof Slick === "undefined") {
         return;
       }
       vScrollDir = 0;
-      for (i = 0, rl = rows.length; i < rl; i++) {
+      rl = rows.length;
+      for (i = 0;  i < rl; i++) {
         if (currentEditor && activeRow === rows[i]) {
           makeActiveCellNormal();
         }
@@ -1662,6 +1780,20 @@ if (typeof Slick === "undefined") {
       invalidateRows([row]);
     }
 
+    function applyFormatResultToCellNode(formatterResult, cellNode, suppressRemove) {
+        if (typeof formatterResult === 'string') { 
+            cellNode.innerHTML = formatterResult;
+            return;
+        }
+        cellNode.innerHTML = formatterResult.text;
+        if (formatterResult.removeClasses && !suppressRemove) { 
+            cellNode.removeClass(formatterResult.removeClasses); 
+        }
+        if (formatterResult.addClasses) { 
+            cellNode.addClass(formatterResult.addClasses); 
+        }
+    }
+
     function updateCell(row, cell) {
       var cellNode = getCellNode(row, cell);
       if (!cellNode) {
@@ -1672,7 +1804,8 @@ if (typeof Slick === "undefined") {
       if (currentEditor && activeRow === row && activeCell === cell) {
         currentEditor.loadValue(d);
       } else {
-        cellNode.innerHTML = d ? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d) : "";
+        var formatterResult =  d ? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d) : "";
+        applyFormatResultToCellNode(formatterResult, cellNode);
         invalidatePostProcessingResults(row);
       }
     }
@@ -1685,7 +1818,7 @@ if (typeof Slick === "undefined") {
 
       ensureCellNodesInRowsCache(row);
 
-      var d = getDataItem(row);
+      var formatterResult, d = getDataItem(row);
     
       for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
         if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
@@ -1699,7 +1832,8 @@ if (typeof Slick === "undefined") {
         if (row === activeRow && columnIdx === activeCell && currentEditor) {
           currentEditor.loadValue(d);
         } else if (d) {
-          node.innerHTML = getFormatter(row, m)(row, columnIdx, getDataItemValueForColumn(d, m), m, d);
+          formatterResult =  getFormatter(row, m)(row, columnIdx, getDataItemValueForColumn(d, m), m, d);
+          applyFormatResultToCellNode(formatterResult, node);
         } else {
           node.innerHTML = "";
         }
@@ -1714,7 +1848,8 @@ if (typeof Slick === "undefined") {
           parseFloat($.css($container[0], "paddingBottom", true)) -
           parseFloat($.css($headerScroller[0], "height")) - getVBoxDelta($headerScroller) -
           (options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0) -
-          (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0);
+          (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0) -
+          (options.createFooterRow && options.showFooterRow ? options.footerRowHeight + getVBoxDelta($footerRowScroller) : 0);
     }
 
     function resizeCanvas() {
@@ -1742,9 +1877,15 @@ if (typeof Slick === "undefined") {
       render();
     }
 
+    function updatePagingStatusFromView( pagingInfo ) {
+        pagingActive = (pagingInfo.pageSize !== 0);
+        pagingIsLastPage = (pagingInfo.pageNum == pagingInfo.totalPages - 1);
+    }
+
     function updateRowCount() {
       if (!initialized) { return; }
 
+      var dataLength = getDataLength();
       var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
       var numberOfRows = dataLengthIncludingAddNew +
           (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0);
@@ -1752,20 +1893,21 @@ if (typeof Slick === "undefined") {
       var oldViewportHasVScroll = viewportHasVScroll;
       // with autoHeight, we do not need to accommodate the vertical scroll bar
       viewportHasVScroll = !options.autoHeight && (numberOfRows * options.rowHeight > viewportH);
+      viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width);
 
       makeActiveCellNormal();
 
       // remove the rows that are now outside of the data range
       // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows
-      var l = dataLengthIncludingAddNew - 1;
+      var r1 = dataLength - 1;
       for (var i in rowsCache) {
-        if (i >= l) {
+        if (i > r1) {
           removeRowFromCache(i);
         }
       }
       if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
 
-      if (activeCellNode && activeRow > l) {
+      if (activeCellNode && activeRow > r1) {
         resetActiveCell();
       }
 
@@ -2114,6 +2256,13 @@ if (typeof Slick === "undefined") {
       }
     }
 
+    function handleFooterRowScroll() {
+      var scrollLeft = $footerRowScroller[0].scrollLeft;
+      if (scrollLeft != $viewport[0].scrollLeft) {
+        $viewport[0].scrollLeft = scrollLeft;
+      }
+    }
+
     function handleScroll() {
       scrollTop = $viewport[0].scrollTop;
       scrollLeft = $viewport[0].scrollLeft;
@@ -2125,6 +2274,9 @@ if (typeof Slick === "undefined") {
         $headerScroller[0].scrollLeft = scrollLeft;
         $topPanelScroller[0].scrollLeft = scrollLeft;
         $headerRowScroller[0].scrollLeft = scrollLeft;
+        if (options.createFooterRow) {
+          $footerRowScroller[0].scrollLeft = scrollLeft;
+        }
       }
 
       if (vScrollDist) {
@@ -2263,7 +2415,7 @@ if (typeof Slick === "undefined") {
 
     function addCellCssStyles(key, hash) {
       if (cellCssClasses[key]) {
-        throw "addCellCssStyles: cell CSS hash with key '" + key + "' already exists.";
+        throw new Error("addCellCssStyles: cell CSS hash with key '" + key + "' already exists.");
       }
 
       cellCssClasses[key] = hash;
@@ -2322,7 +2474,7 @@ if (typeof Slick === "undefined") {
     // Interactivity
 
     function handleMouseWheel(e) {
-      var rowNode = $(e.target).closest(".sr")[0];
+      var rowNode = $(e.target).closest(".slick-row")[0];
       if (rowNode != rowNodeFromLastMouseWheelEvent) {
         if (zombieRowNodeFromLastMouseWheelEvent && zombieRowNodeFromLastMouseWheelEvent != rowNode) {
           if (options.enableAsyncPostRenderCleanup && zombieRowPostProcessedFromLastMouseWheelEvent) {
@@ -2386,15 +2538,21 @@ if (typeof Slick === "undefined") {
 
       if (!handled) {
         if (!e.shiftKey && !e.altKey && !e.ctrlKey) {
+          // editor may specify an array of keys to bubble
+          if (options.editable && currentEditor && currentEditor.keyCaptureList) {
+            if (currentEditor.keyCaptureList.indexOf( e.which ) > -1) {
+                return;
+            }
+          }
           if (e.which == keyCode.ESCAPE) {
             if (!getEditorLock().isActive()) {
               return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)
             }
             cancelEditAndSetFocus();
-          } else if (e.which == keyCode.PAGEDOWN) {
+          } else if (e.which == keyCode.PAGE_DOWN) {
             navigatePageDown();
             handled = true;
-          } else if (e.which == keyCode.PAGEUP) {
+          } else if (e.which == keyCode.PAGE_UP) {
             navigatePageUp();
             handled = true;
           } else if (e.which == keyCode.LEFT) {
@@ -2448,7 +2606,7 @@ if (typeof Slick === "undefined") {
         // if this click resulted in some cell child node getting focus,
         // don't steal it back - keyboard events will still bubble up
         // IE9+ seems to default DIVs to tabIndex=0 instead of -1, so check for cell clicks directly.
-        if (e.target != document.activeElement || $(e.target).hasClass("sc")) {
+        if (e.target != document.activeElement || $(e.target).hasClass("slick-cell")) {
           setFocus();
         }
       }
@@ -2463,16 +2621,20 @@ if (typeof Slick === "undefined") {
         return;
       }
       
-      if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) {
+      // this optimisation causes trouble - MLeibman #329
+      //if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) {
+      if (canCellBeActive(cell.row, cell.cell)) {
         if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) {
           scrollRowIntoView(cell.row, false);
-          setActiveCellInternal(getCellNode(cell.row, cell.cell));
+
+          var preClickModeOn = (e.target && e.target.className === Slick.preClickClassName);
+          setActiveCellInternal(getCellNode(cell.row, cell.cell), null, preClickModeOn);
         }
       }
     }
 
     function handleContextMenu(e) {
-      var $cell = $(e.target).closest(".sc", $canvas);
+      var $cell = $(e.target).closest(".slick-cell", $canvas);
       if ($cell.length === 0) {
         return;
       }
@@ -2562,7 +2724,7 @@ if (typeof Slick === "undefined") {
       // read column number from .l<columnNumber> CSS class
       var cls = /l\d+/.exec(cellNode.className);
       if (!cls) {
-        throw "getCellFromNode: cannot get cell - " + cellNode.className;
+        throw new Error("getCellFromNode: cannot get cell - " + cellNode.className);
       }
       return parseInt(cls[0].substr(1, cls[0].length - 1), 10);
     }
@@ -2578,7 +2740,7 @@ if (typeof Slick === "undefined") {
     }
 
     function getCellFromEvent(e) {
-      var $cell = $(e.target).closest(".sc", $canvas);
+      var $cell = $(e.target).closest(".slick-cell", $canvas);
       if (!$cell.length) {
         return null;
       }
@@ -2636,9 +2798,11 @@ if (typeof Slick === "undefined") {
       scrollRowIntoView(row, doPaging);
 
       var colspan = getColspan(row, cell);
-      var left = columnPosLeft[cell],
-        right = columnPosRight[cell + (colspan > 1 ? colspan - 1 : 0)],
-        scrollRight = scrollLeft + viewportW;
+      internalScrollColumnIntoView(columnPosLeft[cell], columnPosRight[cell + (colspan > 1 ? colspan - 1 : 0)]);
+    }
+
+    function internalScrollColumnIntoView(left, right) {
+      var scrollRight = scrollLeft + viewportW;
 
       if (left < scrollLeft) {
         $viewport.scrollLeft(left);
@@ -2651,7 +2815,11 @@ if (typeof Slick === "undefined") {
       }
     }
 
-    function setActiveCellInternal(newCell, opt_editMode) {
+    function scrollColumnIntoView(cell) {
+      internalScrollColumnIntoView(columnPosLeft[cell], columnPosRight[cell]);
+    }
+
+    function setActiveCellInternal(newCell, opt_editMode, preClickModeOn, suppressActiveCellChangedEvent) {
       if (activeCellNode !== null) {
         makeActiveCellNormal();
         $(activeCellNode).removeClass("active");
@@ -2671,27 +2839,30 @@ if (typeof Slick === "undefined") {
           opt_editMode = (activeRow == getDataLength()) || options.autoEdit;
         }
 
+        if (options.showCellSelection) {
         $(activeCellNode).addClass("active");
         $(rowsCache[activeRow].rowNode).addClass("active");
+        }
 
         if (options.editable && opt_editMode && isCellPotentiallyEditable(activeRow, activeCell)) {
           clearTimeout(h_editorLoader);
 
           if (options.asyncEditorLoading) {
             h_editorLoader = setTimeout(function () {
-              makeActiveCellEditable();
+              makeActiveCellEditable(undefined, preClickModeOn);
             }, options.asyncEditorLoadDelay);
           } else {
-            makeActiveCellEditable();
+            makeActiveCellEditable(undefined, preClickModeOn);
           }
         }
       } else {
         activeRow = activeCell = null;
       }
     
-      if (activeCellChanged) {
-        trigger(self.onActiveCellChanged, getActiveCell());
-      }
+      // this optimisation causes trouble - MLeibman #329
+      //if (activeCellChanged) {
+      if (!suppressActiveCellChangedEvent) { trigger(self.onActiveCellChanged, getActiveCell()); }
+      //}
     }
 
     function clearTextSelection() {
@@ -2742,7 +2913,8 @@ if (typeof Slick === "undefined") {
         if (d) {
           var column = columns[activeCell];
           var formatter = getFormatter(activeRow, column);
-          activeCellNode.innerHTML = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, d, self);
+          var formatterResult =  formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, d, self);
+          applyFormatResultToCellNode(formatterResult, activeCellNode);
           invalidatePostProcessingResults(activeRow);
         }
       }
@@ -2756,12 +2928,12 @@ if (typeof Slick === "undefined") {
       getEditorLock().deactivate(editController);
     }
 
-    function makeActiveCellEditable(editor) {
+    function makeActiveCellEditable(editor, preClickModeOn) {
       if (!activeCellNode) {
         return;
       }
       if (!options.editable) {
-        throw "Grid : makeActiveCellEditable : should never get called when options.editable is false";
+        throw new Error("Grid : makeActiveCellEditable : should never get called when options.editable is false");
       }
 
       // cancel pending async call if there is one
@@ -2782,7 +2954,7 @@ if (typeof Slick === "undefined") {
       getEditorLock().activate(editController);
       $(activeCellNode).addClass("editable");
 
-	  var useEditor = editor || getEditor(activeRow, activeCell);
+      var useEditor = editor || getEditor(activeRow, activeCell);
 
       // don't clear the cell if a custom editor is passed through
       if (!editor && !useEditor.suppressClearOnEdit) {
@@ -2802,6 +2974,9 @@ if (typeof Slick === "undefined") {
 
       if (item) {
         currentEditor.loadValue(item);
+        if (preClickModeOn && currentEditor.preClick) {
+          currentEditor.preClick();
+        }
       }
 
       serializedEditorValue = currentEditor.serializeValue();
@@ -2843,7 +3018,7 @@ if (typeof Slick === "undefined") {
       // walk up the tree
       var offsetParent = elem.offsetParent;
       while ((elem = elem.parentNode) != document.body) {
-		if (elem == null) break;
+        if (elem == null) break;
 
         if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible") {
           box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight;
@@ -3132,6 +3307,10 @@ if (typeof Slick === "undefined") {
 
       var firstFocusableCell = null;
       var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
+
+      // if at last row, cycle through columns rather than get stuck in the last one
+      if (row === dataLengthIncludingAddNew - 1) { row--; }
+
       while (++row < dataLengthIncludingAddNew) {
         firstFocusableCell = findFirstFocusableCell(row);
         if (firstFocusableCell !== null) {
@@ -3264,7 +3443,7 @@ if (typeof Slick === "undefined") {
       return null;
     }
 
-    function setActiveCell(row, cell) {
+    function setActiveCell(row, cell, opt_editMode, preClickModeOn, suppressActiveCellChangedEvent) {
       if (!initialized) { return; }
       if (row > getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
         return;
@@ -3275,7 +3454,7 @@ if (typeof Slick === "undefined") {
       }
 
       scrollCellIntoView(row, cell, false);
-      setActiveCellInternal(getCellNode(row, cell), false);
+      setActiveCellInternal(getCellNode(row, cell), opt_editMode, preClickModeOn, suppressActiveCellChangedEvent);
     }
 
     function canCellBeActive(row, cell) {
@@ -3442,14 +3621,14 @@ if (typeof Slick === "undefined") {
 
     function getSelectedRows() {
       if (!selectionModel) {
-        throw "Selection model is not set";
+        throw new Error("Selection model is not set");
       }
       return selectedRows;
     }
 
     function setSelectedRows(rows) {
       if (!selectionModel) {
-        throw "Selection model is not set";
+        throw new Error("Selection model is not set");
       }
       selectionModel.setSelectedRanges(rowsToRanges(rows));
     }
@@ -3483,7 +3662,7 @@ if (typeof Slick === "undefined") {
     // Public API
 
     $.extend(this, {
-      "slickGridVersion": "2.2.4",
+      "slickGridVersion": "2.3.4",
 
       // Events
       "onScroll": new Slick.Event(),
@@ -3495,7 +3674,9 @@ if (typeof Slick === "undefined") {
       "onHeaderCellRendered": new Slick.Event(),
       "onBeforeHeaderCellDestroy": new Slick.Event(),
       "onHeaderRowCellRendered": new Slick.Event(),
+      "onFooterRowCellRendered": new Slick.Event(),
       "onBeforeHeaderRowCellDestroy": new Slick.Event(),
+      "onBeforeFooterRowCellDestroy": new Slick.Event(),
       "onMouseEnter": new Slick.Event(),
       "onMouseLeave": new Slick.Event(),
       "onClick": new Slick.Event(),
@@ -3503,6 +3684,7 @@ if (typeof Slick === "undefined") {
       "onContextMenu": new Slick.Event(),
       "onKeyDown": new Slick.Event(),
       "onAddNewRow": new Slick.Event(),
+      "onBeforeAppendCell": new Slick.Event(),
       "onValidationError": new Slick.Event(),
       "onViewportChanged": new Slick.Event(),
       "onColumnsReordered": new Slick.Event(),
@@ -3542,6 +3724,7 @@ if (typeof Slick === "undefined") {
       "getSelectedRows": getSelectedRows,
       "setSelectedRows": setSelectedRows,
       "getContainerNode": getContainerNode,
+      "updatePagingStatusFromView": updatePagingStatusFromView,
 
       "render": render,
       "invalidate": invalidate,
@@ -3587,6 +3770,9 @@ if (typeof Slick === "undefined") {
       "setHeaderRowVisibility": setHeaderRowVisibility,
       "getHeaderRow": getHeaderRow,
       "getHeaderRowColumn": getHeaderRowColumn,
+      "setFooterRowVisibility": setFooterRowVisibility,
+      "getFooterRow": getFooterRow,
+      "getFooterRowColumn": getFooterRowColumn,
       "getGridPosition": getGridPosition,
       "flashCell": flashCell,
       "addCellCssStyles": addCellCssStyles,
diff --git a/web/pgadmin/static/vendor/slickgrid/slick.groupitemmetadataprovider.js b/web/pgadmin/static/vendor/slickgrid/slick.groupitemmetadataprovider.js
new file mode 100644
index 00000000..de05c721
--- /dev/null
+++ b/web/pgadmin/static/vendor/slickgrid/slick.groupitemmetadataprovider.js
@@ -0,0 +1,158 @@
+(function ($) {
+  $.extend(true, window, {
+    Slick: {
+      Data: {
+        GroupItemMetadataProvider: GroupItemMetadataProvider
+      }
+    }
+  });
+
+
+  /***
+   * Provides item metadata for group (Slick.Group) and totals (Slick.Totals) rows produced by the DataView.
+   * This metadata overrides the default behavior and formatting of those rows so that they appear and function
+   * correctly when processed by the grid.
+   *
+   * This class also acts as a grid plugin providing event handlers to expand & collapse groups.
+   * If "grid.registerPlugin(...)" is not called, expand & collapse will not work.
+   *
+   * @class GroupItemMetadataProvider
+   * @module Data
+   * @namespace Slick.Data
+   * @constructor
+   * @param options
+   */
+  function GroupItemMetadataProvider(options) {
+    var _grid;
+    var _defaults = {
+      groupCssClass: "slick-group",
+      groupTitleCssClass: "slick-group-title",
+      totalsCssClass: "slick-group-totals",
+      groupFocusable: true,
+      totalsFocusable: false,
+      toggleCssClass: "slick-group-toggle",
+      toggleExpandedCssClass: "expanded",
+      toggleCollapsedCssClass: "collapsed",
+      enableExpandCollapse: true,
+      groupFormatter: defaultGroupCellFormatter,
+      totalsFormatter: defaultTotalsCellFormatter
+    };
+
+    options = $.extend(true, {}, _defaults, options);
+
+
+    function defaultGroupCellFormatter(row, cell, value, columnDef, item) {
+      if (!options.enableExpandCollapse) {
+        return item.title;
+      }
+
+      var indentation = item.level * 15 + "px";
+
+      return "<span class='" + options.toggleCssClass + " " +
+          (item.collapsed ? options.toggleCollapsedCssClass : options.toggleExpandedCssClass) +
+          "' style='margin-left:" + indentation +"'>" +
+          "</span>" +
+          "<span class='" + options.groupTitleCssClass + "' level='" + item.level + "'>" +
+            item.title +
+          "</span>";
+    }
+
+    function defaultTotalsCellFormatter(row, cell, value, columnDef, item) {
+      return (columnDef.groupTotalsFormatter && columnDef.groupTotalsFormatter(item, columnDef)) || "";
+    }
+
+
+    function init(grid) {
+      _grid = grid;
+      _grid.onClick.subscribe(handleGridClick);
+      _grid.onKeyDown.subscribe(handleGridKeyDown);
+
+    }
+
+    function destroy() {
+      if (_grid) {
+        _grid.onClick.unsubscribe(handleGridClick);
+        _grid.onKeyDown.unsubscribe(handleGridKeyDown);
+      }
+    }
+
+    function handleGridClick(e, args) {
+      var item = this.getDataItem(args.row);
+      if (item && item instanceof Slick.Group && $(e.target).hasClass(options.toggleCssClass)) {
+        var range = _grid.getRenderedRange();
+        this.getData().setRefreshHints({
+          ignoreDiffsBefore: range.top,
+          ignoreDiffsAfter: range.bottom + 1
+        });
+
+        if (item.collapsed) {
+          this.getData().expandGroup(item.groupingKey);
+        } else {
+          this.getData().collapseGroup(item.groupingKey);
+        }
+
+        e.stopImmediatePropagation();
+        e.preventDefault();
+      }
+    }
+
+    // TODO:  add -/+ handling
+    function handleGridKeyDown(e, args) {
+      if (options.enableExpandCollapse && (e.which == $.ui.keyCode.SPACE)) {
+        var activeCell = this.getActiveCell();
+        if (activeCell) {
+          var item = this.getDataItem(activeCell.row);
+          if (item && item instanceof Slick.Group) {
+            var range = _grid.getRenderedRange();
+            this.getData().setRefreshHints({
+              ignoreDiffsBefore: range.top,
+              ignoreDiffsAfter: range.bottom + 1
+            });
+
+            if (item.collapsed) {
+              this.getData().expandGroup(item.groupingKey);
+            } else {
+              this.getData().collapseGroup(item.groupingKey);
+            }
+
+            e.stopImmediatePropagation();
+            e.preventDefault();
+          }
+        }
+      }
+    }
+
+    function getGroupRowMetadata(item) {
+      return {
+        selectable: false,
+        focusable: options.groupFocusable,
+        cssClasses: options.groupCssClass,
+        columns: {
+          0: {
+            colspan: "*",
+            formatter: options.groupFormatter,
+            editor: null
+          }
+        }
+      };
+    }
+
+    function getTotalsRowMetadata(item) {
+      return {
+        selectable: false,
+        focusable: options.totalsFocusable,
+        cssClasses: options.totalsCssClass,
+        formatter: options.totalsFormatter,
+        editor: null
+      };
+    }
+
+
+    return {
+      "init": init,
+      "destroy": destroy,
+      "getGroupRowMetadata": getGroupRowMetadata,
+      "getTotalsRowMetadata": getTotalsRowMetadata
+    };
+  }
+})(jQuery);
diff --git a/web/pgadmin/static/vendor/slickgrid/slick.remotemodel-yahoo.js b/web/pgadmin/static/vendor/slickgrid/slick.remotemodel-yahoo.js
new file mode 100644
index 00000000..fb11d85a
--- /dev/null
+++ b/web/pgadmin/static/vendor/slickgrid/slick.remotemodel-yahoo.js
@@ -0,0 +1,206 @@
+(function ($) {
+  /***
+   * A sample AJAX data store implementation.
+   * Right now, it's hooked up to load Hackernews stories, but can
+   * easily be extended to support any JSONP-compatible backend that accepts paging parameters.
+   */
+  function RemoteModel() {
+    // private
+    var PAGESIZE = 10;
+    var data = {length: 0};
+    var h_request = null;
+    var req = null; // ajax request
+
+    // events
+    var onDataLoading = new Slick.Event();
+    var onDataLoaded = new Slick.Event();
+
+
+    function init() {
+    }
+
+
+    function isDataLoaded(from, to) {
+      for (var i = from; i <= to; i++) {
+        if (data[i] == undefined || data[i] == null) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+
+    function clear() {
+      for (var key in data) {
+        delete data[key];
+      }
+      data.length = 0;
+    }
+
+    function ensureData(from, to) {
+      if (req) {
+        req.abort();
+        for (var i = req.fromPage; i <= req.toPage; i++) {
+          data[i * PAGESIZE] = undefined;
+        }
+      }
+
+      if (from < 0) {
+        from = 0;
+      }
+
+      if (data.length > 0) {
+        to = Math.min(to, data.length - 1);
+      }
+
+      var fromPage = Math.floor(from / PAGESIZE);
+      var toPage = Math.floor(to / PAGESIZE);
+
+      while (data[fromPage * PAGESIZE] !== undefined && fromPage < toPage)
+        fromPage++;
+
+      while (data[toPage * PAGESIZE] !== undefined && fromPage < toPage)
+        toPage--;
+
+      if (fromPage > toPage || ((fromPage == toPage) && data[fromPage * PAGESIZE] !== undefined)) {
+        // TODO:  look-ahead
+        onDataLoaded.notify({from: from, to: to});
+        return;
+      }
+
+      var recStart = (fromPage * PAGESIZE);
+      var recCount = (((toPage - fromPage) * PAGESIZE) + PAGESIZE);
+
+      var url = "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20rss"
+        + "(" + recStart + "%2C" + recCount + ")"
+        + "%20where%20url%3D%22http%3A%2F%2Frss.news.yahoo.com%2Frss%2Ftopstories%22"
+        + "&format=json";
+
+      if (h_request != null) {
+        clearTimeout(h_request);
+      }
+
+      h_request = setTimeout(function () {
+        for (var i = fromPage; i <= toPage; i++)
+          data[i * PAGESIZE] = null; // null indicates a 'requested but not available yet'
+
+        onDataLoading.notify({from: from, to: to});
+
+        req = $.jsonp({
+          url: url,
+          callbackParameter: "callback",
+          cache: true,
+          success: function (json, textStatus, xOptions) {
+            onSuccess(json, recStart)
+          },
+          error: function () {
+            onError(fromPage, toPage)
+          }
+        });
+
+        req.fromPage = fromPage;
+        req.toPage = toPage;
+      }, 50);
+    }
+
+
+    function onError(fromPage, toPage) {
+      alert("error loading pages " + fromPage + " to " + toPage);
+    }
+
+// SAMPLE DATA
+//    {
+//      "query": {
+//        "count": 40,
+//        "created": "2015-03-03T00:34:00Z",
+//        "lang": "en-US",
+//        "results": {
+//          "item": [
+//            {
+//              "title": "Netanyahu assails Iran deal, touts US-Israel ties",
+//              "description": "<p><a href=\"http://news.yahoo.com/netanyahu-us-officials-face-off-iran-133539021--politics.html\"><img src=\"http://l2.yimg.com/bt/api/res/1.2/4eoBxbJStrbGAKbmBYOJfg--/YXBwaWQ9eW5ld3M7Zmk9ZmlsbDtoPTg2O3E9NzU7dz0xMzA-/http://media.zenfs.com/en_us/News/ap_webfeeds/2f3a20c2d46d9f096f0f6a706700d430.jpg\" width=\"130\" height=\"86\" alt=\"Israeli Prime Minister Benjamin Netanyahu addresses the 2015 American Israel Public Affairs Committee (AIPAC) Policy Conference in Washington, Monday, March 2, 2015. (AP Photo/Cliff Owen)\" align=\"left\" title=\"Israeli Prime Minister Benjamin Netanyahu addresses the 2015 American Israel Public Affairs Committee (AIPAC) Policy Conference in Washington, Monday, March 2, 2015. (AP Photo/Cliff Owen)\" border=\"0\" /></a>WASHINGTON (AP) — Seeking to lower tensions, Benjamin Netanyahu and U.S. officials cast their dispute over Iran as a family squabble on Monday, even as the Israeli leader claimed President Barack Obama did not — and could not — fully understand his nation&#039;s vital security concerns.</p><br clear=\"all\"/>",
+//              "link": "http://news.yahoo.com/netanyahu-us-officials-face-off-iran-133539021--politics.html",
+//              "pubDate": "Mon, 02 Mar 2015 19:17:36 -0500",
+//              "source": {
+//                "url": "http://www.ap.org/",
+//                "content": "Associated Press"
+//              },
+//              "guid": {
+//                "isPermaLink": "false",
+//                "content": "netanyahu-us-officials-face-off-iran-133539021--politics"
+//              },
+//              "content": {
+//                "height": "86",
+//                "type": "image/jpeg",
+//                "url": "http://l2.yimg.com/bt/api/res/1.2/4eoBxbJStrbGAKbmBYOJfg--/YXBwaWQ9eW5ld3M7Zmk9ZmlsbDtoPTg2O3E9NzU7dz0xMzA-/http://media.zenfs.com/en_us/News/ap_webfeeds/2f3a20c2d46d9f096f0f6a706700d430.jpg",
+//                "width": "130"
+//              },
+//              "text": {
+//                "type": "html",
+//                "content": "<p><a href=\"http://news.yahoo.com/netanyahu-us-officials-face-off-iran-133539021--politics.html\"><img src=\"http://l2.yimg.com/bt/api/res/1.2/4eoBxbJStrbGAKbmBYOJfg--/YXBwaWQ9eW5ld3M7Zmk9ZmlsbDtoPTg2O3E9NzU7dz0xMzA-/http://media.zenfs.com/en_us/News/ap_webfeeds/2f3a20c2d46d9f096f0f6a706700d430.jpg\" width=\"130\" height=\"86\" alt=\"Israeli Prime Minister Benjamin Netanyahu addresses the 2015 American Israel Public Affairs Committee (AIPAC) Policy Conference in Washington, Monday, March 2, 2015. (AP Photo/Cliff Owen)\" align=\"left\" title=\"Israeli Prime Minister Benjamin Netanyahu addresses the 2015 American Israel Public Affairs Committee (AIPAC) Policy Conference in Washington, Monday, March 2, 2015. (AP Photo/Cliff Owen)\" border=\"0\" /></a>WASHINGTON (AP) — Seeking to lower tensions, Benjamin Netanyahu and U.S. officials cast their dispute over Iran as a family squabble on Monday, even as the Israeli leader claimed President Barack Obama did not — and could not — fully understand his nation&#039;s vital security concerns.</p><br clear=\"all\"/>"
+//              },
+//              "credit": {
+//                "role": "publishing company"
+//              }
+//            },
+//            {... },
+//            {... },
+//          ]
+//        }
+//      }
+//    }
+
+    function onSuccess(json, recStart) {
+      var recEnd = recStart;
+      if (json.query.count > 0) {
+        var results = json.query.results.item;
+        recEnd = recStart + results.length;
+        data.length = 100;// Math.min(parseInt(results.length),1000); // limitation of the API
+
+        for (var i = 0; i < results.length; i++) {
+          var item = results[i];
+
+          item.pubDate = new Date(item.pubDate);
+
+          data[recStart + i] = { index: recStart + i };
+          data[recStart + i].pubDate = item.pubDate;
+          data[recStart + i].title = item.title;
+          data[recStart + i].url = item.link;
+          data[recStart + i].text = item.description;
+        }
+      }
+      req = null;
+
+      onDataLoaded.notify({from: recStart, to: recEnd});
+    }
+
+
+    function reloadData(from, to) {
+      for (var i = from; i <= to; i++)
+        delete data[i];
+
+      ensureData(from, to);
+    }
+
+    init();
+
+    return {
+      // properties
+      "data": data,
+
+      // methods
+      "clear": clear,
+      "isDataLoaded": isDataLoaded,
+      "ensureData": ensureData,
+      "reloadData": reloadData,
+
+      // events
+      "onDataLoading": onDataLoading,
+      "onDataLoaded": onDataLoaded
+    };
+  }
+
+  // Slick.Data.RemoteModel
+  $.extend(true, window, { Slick: { Data: { RemoteModel: RemoteModel }}});
+})(jQuery);
diff --git a/web/pgadmin/static/vendor/slickgrid/slick.remotemodel.js b/web/pgadmin/static/vendor/slickgrid/slick.remotemodel.js
new file mode 100644
index 00000000..f81ca3f4
--- /dev/null
+++ b/web/pgadmin/static/vendor/slickgrid/slick.remotemodel.js
@@ -0,0 +1,169 @@
+(function ($) {
+  /***
+   * A sample AJAX data store implementation.
+   * Right now, it's hooked up to load search results from Octopart, but can
+   * easily be extended to support any JSONP-compatible backend that accepts paging parameters.
+   */
+  function RemoteModel() {
+    // private
+    var PAGESIZE = 50;
+    var data = {length: 0};
+    var searchstr = "";
+    var sortcol = null;
+    var sortdir = 1;
+    var h_request = null;
+    var req = null; // ajax request
+
+    // events
+    var onDataLoading = new Slick.Event();
+    var onDataLoaded = new Slick.Event();
+
+
+    function init() {
+    }
+
+
+    function isDataLoaded(from, to) {
+      for (var i = from; i <= to; i++) {
+        if (data[i] == undefined || data[i] == null) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+
+    function clear() {
+      for (var key in data) {
+        delete data[key];
+      }
+      data.length = 0;
+    }
+
+
+    function ensureData(from, to) {
+      if (req) {
+        req.abort();
+        for (var i = req.fromPage; i <= req.toPage; i++)
+          data[i * PAGESIZE] = undefined;
+      }
+
+      if (from < 0) {
+        from = 0;
+      }
+
+      if (data.length > 0) {
+        to = Math.min(to, data.length - 1);
+      }
+
+      var fromPage = Math.floor(from / PAGESIZE);
+      var toPage = Math.floor(to / PAGESIZE);
+
+      while (data[fromPage * PAGESIZE] !== undefined && fromPage < toPage)
+        fromPage++;
+
+      while (data[toPage * PAGESIZE] !== undefined && fromPage < toPage)
+        toPage--;
+
+      if (fromPage > toPage || ((fromPage == toPage) && data[fromPage * PAGESIZE] !== undefined)) {
+        // TODO:  look-ahead
+        onDataLoaded.notify({from: from, to: to});
+        return;
+      }
+
+      var url = "http://octopart.com/api/v3/parts/search?apikey=68b25f31&include[]=short_description&show[]=uid&show[]=manufacturer&show[]=mpn&show[]=brand&show[]=octopart_url&show[]=short_description&q=" + searchstr + "&start=" + (fromPage * PAGESIZE) + "&limit=" + (((toPage - fromPage) * PAGESIZE) + PAGESIZE);
+
+      if (sortcol != null) {
+        url += ("&sortby=" + sortcol + ((sortdir > 0) ? "+asc" : "+desc"));
+      }
+
+      if (h_request != null) {
+        clearTimeout(h_request);
+      }
+
+      h_request = setTimeout(function () {
+        for (var i = fromPage; i <= toPage; i++)
+          data[i * PAGESIZE] = null; // null indicates a 'requested but not available yet'
+
+        onDataLoading.notify({from: from, to: to});
+
+        req = $.jsonp({
+          url: url,
+          callbackParameter: "callback",
+          cache: true,
+          success: onSuccess,
+          error: function () {
+            onError(fromPage, toPage)
+          }
+        });
+        req.fromPage = fromPage;
+        req.toPage = toPage;
+      }, 50);
+    }
+
+
+    function onError(fromPage, toPage) {
+      alert("error loading pages " + fromPage + " to " + toPage);
+    }
+
+    function onSuccess(resp) {
+      var from = resp.request.start, to = from + resp.results.length;
+      data.length = Math.min(parseInt(resp.hits),1000); // limitation of the API
+
+      for (var i = 0; i < resp.results.length; i++) {
+        var item = resp.results[i].item;
+
+        data[from + i] = item;
+        data[from + i].index = from + i;
+      }
+
+      req = null;
+
+      onDataLoaded.notify({from: from, to: to});
+    }
+
+
+    function reloadData(from, to) {
+      for (var i = from; i <= to; i++)
+        delete data[i];
+
+      ensureData(from, to);
+    }
+
+
+    function setSort(column, dir) {
+      sortcol = column;
+      sortdir = dir;
+      clear();
+    }
+
+    function setSearch(str) {
+      searchstr = str;
+      clear();
+    }
+
+
+    init();
+
+    return {
+      // properties
+      "data": data,
+
+      // methods
+      "clear": clear,
+      "isDataLoaded": isDataLoaded,
+      "ensureData": ensureData,
+      "reloadData": reloadData,
+      "setSort": setSort,
+      "setSearch": setSearch,
+
+      // events
+      "onDataLoading": onDataLoading,
+      "onDataLoaded": onDataLoaded
+    };
+  }
+
+  // Slick.Data.RemoteModel
+  $.extend(true, window, { Slick: { Data: { RemoteModel: RemoteModel }}});
+})(jQuery);
\ No newline at end of file
