diff --git a/libraries.txt b/libraries.txt
index 405d76f..0e397c8 100644
--- a/libraries.txt
+++ b/libraries.txt
@@ -1,24 +1,28 @@
 This is a list of the third party libraries/code used in the application, not
 including Python modules installed via Pip which are listed in requirements.txt.
 
-Library           Version     Licence      URL
-=======           =======     =======      ===
-QT                4.6.2+      LGPL v2.1/3  http://www.qt.io/ 
-Bootstrap         3.3.4       MIT          http://getbootstrap.com/
-jQuery            1.11.1      MIT          http://jquery.com/
-Modernizr         2.6.2       MIT/BSD      http://modernizr.com/
-AlertifyJS        1.1.0       MIT          http://alertifyjs.com/
-CodeMirror        4.12        MIT          http://codemirror.net/       
-aciTree           4.5.0-rc.7  MIT/GPL      http://acoderinsights.ro/en/aciTree-tree-view-with-jQuery
-wcDocker          8b84d55415  MIT/GPL      https://github.com/WebCabin/wcDocker
-Require.js        2.1.18      BSD/MIT      http://requirejs.org/
-Underscore.js     1.8.3       MIT          http://underscorejs.org/
-Underscore.string 387ab72d49  MIT          http://epeli.github.io/underscore.string/
-Backform.js       5859b4f9db  MIT          https://github.com/AmiliaApp/backform
-Backbone          1.1.2       MIT          http://backbonejs.org
-font-Awesome      4.3         SIL OFL      http://fortawesome.github.io/Font-Awesome/
-font-mfizz        1.2         MIT          http://fizzed.com/oss/font-mfizz
-backgrid.js       0.3.5       MIT          http://backgridjs.com/
-backbone.undo     0.2         MIT          http://backbone.undojs.com/
-bootstrap-switch  3.3.2       MIT          http://www.bootstrap-switch.org/
-select2           4.0.1       MIT          https://select2.github.io/
+Library             Version     Licence      URL
+=======             =======     =======      ===
+QT                  4.6.2+      LGPL v2.1/3  http://www.qt.io/
+Bootstrap           3.3.4       MIT          http://getbootstrap.com/
+jQuery              1.11.1      MIT          http://jquery.com/
+Modernizr           2.6.2       MIT/BSD      http://modernizr.com/
+AlertifyJS          1.1.0       MIT          http://alertifyjs.com/
+CodeMirror          4.12        MIT          http://codemirror.net/
+aciTree             4.5.0-rc.7  MIT/GPL      http://acoderinsights.ro/en/aciTree-tree-view-with-jQuery
+wcDocker            8b84d55415  MIT/GPL      https://github.com/WebCabin/wcDocker
+Require.js          2.1.18      BSD/MIT      http://requirejs.org/
+Underscore.js       1.8.3       MIT          http://underscorejs.org/
+Underscore.string   387ab72d49  MIT          http://epeli.github.io/underscore.string/
+Backform.js         5859b4f9db  MIT          https://github.com/AmiliaApp/backform
+Backbone            1.1.2       MIT          http://backbonejs.org
+font-Awesome        4.3         SIL OFL      http://fortawesome.github.io/Font-Awesome/
+font-mfizz          1.2         MIT          http://fizzed.com/oss/font-mfizz
+backgrid.js         0.3.5       MIT          http://backgridjs.com/
+backbone.undo       0.2         MIT          http://backbone.undojs.com/
+bootstrap-switch    3.3.2       MIT          http://www.bootstrap-switch.org/
+select2             4.0.1       MIT          https://select2.github.io/
+backgrid-filter     01b2b21     MIT          https://github.com/wyuenho/backgrid-filter
+backbone.paginator  2.0.3       MIT          http://github.com/backbone-paginator/backbone.paginator
+backgrid-paginator  03632df     MIT          https://github.com/wyuenho/backgrid-paginator
+backgrid-select-all 1a00053     MIT          https://github.com/wyuenho/backgrid-select-all
\ No newline at end of file
diff --git a/web/pgadmin/static/css/backgrid/backgrid-filter.css b/web/pgadmin/static/css/backgrid/backgrid-filter.css
new file mode 100644
index 0000000..8931b07
--- /dev/null
+++ b/web/pgadmin/static/css/backgrid/backgrid-filter.css
@@ -0,0 +1,201 @@
+/*
+  backgrid-filter
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+
+/*
+  Search Icon CSS derived from:
+
+  PURE CSS GUI ICONS
+  by Nicolas Gallagher
+  - http://nicolasgallagher.com/pure-css-gui-icons/
+
+  http://nicolasgallagher.com
+  http://twitter.com/necolas
+
+  Created: 29 July 2010
+  Version: 1.0.1
+
+  Dual licensed under MIT and GNU GPLv2 (c) Nicolas Gallagher
+*/
+
+.backgrid-filter.form-search {
+  position: relative;
+  width: 248px;
+  height: 30px;
+  margin: 20px;
+}
+
+/*
+  Search Icon
+*/
+
+.backgrid-filter .search {
+  position: absolute;
+  top: 50%;
+  left: 6px;
+  z-index: 1000;
+  width: 10px;
+  height: 20px;
+  margin-top: -10px;
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+}
+
+.backgrid-filter .search:before {
+  position: absolute;
+  top: 50%;
+  left: 0;
+  width: 6px;
+  height: 6px;
+  margin-top: -6px;
+  background: transparent;
+  border: 3px solid gray;
+  -webkit-border-radius: 12px;
+     -moz-border-radius: 12px;
+          border-radius: 12px;
+  content: "";
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+}
+
+.backgrid-filter .search:after {
+  position: absolute;
+  top: 50%;
+  left: 10px;
+  width: 3px;
+  height: 7px;
+  margin-top: 2px;
+  background-color: gray;
+  content: "";
+  -webkit-transform: rotate(-45deg);
+     -moz-transform: rotate(-45deg);
+      -ms-transform: rotate(-45deg);
+       -o-transform: rotate(-45deg);
+          transform: rotate(-45deg);
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+}
+
+/*
+  Clear button
+ */
+
+.backgrid-filter .clear {
+  position: absolute;
+  top: 50%;
+  right: 8px;
+  z-index: 1000;
+  width: 10px;
+  height: 20px;
+  margin-top: -10px;
+  font-family: sans-serif;
+  font-size: 20px;
+  font-weight: bold;
+  line-height: 20px;
+  color: gray;
+  text-decoration: none;
+}
+
+.backgrid-filter input[type="search"] {
+  position: absolute;
+  display: inline-block;
+  width: 206px;
+  height: 20px;
+  padding: 4px 6px;
+  font-weight: normal;
+  color: #555;
+  vertical-align: middle;
+  background-color: #fff;
+  border: 1px solid #ccc;
+  -webkit-border-radius: 4px;
+     -moz-border-radius: 4px;
+          border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+     -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+          box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
+     -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
+       -o-transition: border linear 0.2s, box-shadow linear 0.2s;
+          transition: border linear 0.2s, box-shadow linear 0.2s;
+}
+
+/*
+  Normalize the search input box, with code borrowed from normalize.css.
+
+  https://github.com/necolas/normalize.css/
+
+  Copyright (c) Nicolas Gallagher and Jonathan Neal, MIT @license.
+ */
+
+/*
+ * 1. Correct font family not being inherited in all browsers.
+ * 2. Correct font size not being inherited in all browsers.
+ * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
+ * 4. Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet.
+ */
+
+.backgrid-filter input {
+  margin: 0;
+  font-family: inherit;
+  font-size: 100%;
+  line-height: normal;
+}
+
+/*
+ * Re-set default cursor for disabled elements.
+ */
+
+.backgrid-filter input[disabled] {
+  cursor: default;
+}
+
+/*
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ *    (include `-moz` to future-proof).
+ */
+
+.backgrid-filter input[type="search"] {
+  outline: none;
+  -webkit-box-sizing: content-box;
+     -moz-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-appearance: none;
+}
+
+/*
+ * Remove the default clear button on IE
+ */
+
+.backgrid-filter input[type="search"]::-ms-clear {
+  display: none;
+}
+
+/*
+ * Remove the default clear button on WebKit browsers
+ */
+
+.backgrid-filter input[type="search"]::-webkit-search-cancel-button {
+  -webkit-appearance: none;
+}
+
+/*
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+.backgrid-filter input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+.backgrid-filter input[type="search"] {
+  padding-right: 18px;
+  padding-left: 22px;
+}
\ No newline at end of file
diff --git a/web/pgadmin/static/css/backgrid/backgrid-filter.min.css b/web/pgadmin/static/css/backgrid/backgrid-filter.min.css
new file mode 100644
index 0000000..5f67d69
--- /dev/null
+++ b/web/pgadmin/static/css/backgrid/backgrid-filter.min.css
@@ -0,0 +1 @@
+.backgrid-filter.form-search{position:relative;width:248px;height:30px;margin:20px}.backgrid-filter .search{position:absolute;top:50%;left:6px;z-index:1000;width:10px;height:20px;margin-top:-10px;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.backgrid-filter .search:before{position:absolute;top:50%;left:0;width:6px;height:6px;margin-top:-6px;background:transparent;border:3px solid gray;-webkit-border-radius:12px;-moz-border-radius:12px;border-radius:12px;content:"";-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.backgrid-filter .search:after{position:absolute;top:50%;left:10px;width:3px;height:7px;margin-top:2px;background-color:gray;content:"";-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.backgrid-filter .clear{position:absolute;top:50%;right:8px;z-index:1000;width:10px;height:20px;margin-top:-10px;font-family:sans-serif;font-size:20px;font-weight:bold;line-height:20px;color:gray;text-decoration:none}.backgrid-filter input[type="search"]{position:absolute;display:inline-block;width:206px;height:20px;padding:4px 6px;font-weight:normal;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #ccc;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}.backgrid-filter input{margin:0;font-family:inherit;font-size:100%;line-height:normal}.backgrid-filter input[disabled]{cursor:default}.backgrid-filter input[type="search"]{outline:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:none}.backgrid-filter input[type="search"]::-ms-clear{display:none}.backgrid-filter input::-moz-focus-inner{padding:0;border:0}.backgrid-filter input[type="search"]{padding-right:18px;padding-left:22px}
\ No newline at end of file
diff --git a/web/pgadmin/static/css/backgrid/backgrid-paginator.css b/web/pgadmin/static/css/backgrid/backgrid-paginator.css
new file mode 100644
index 0000000..20ebbd2
--- /dev/null
+++ b/web/pgadmin/static/css/backgrid/backgrid-paginator.css
@@ -0,0 +1,58 @@
+/*
+  backgrid-paginator
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT license.
+*/
+
+.backgrid-paginator {
+  text-align: center;
+  border-top: none;
+  -webkit-border-radius: 0 0 4px 4px;
+     -moz-border-radius: 0 0 4px 4px;
+          border-radius: 0 0 4px 4px;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.backgrid-paginator ul {
+  display: inline-block;
+  *display: inline;
+  margin: 5px 0;
+  *zoom: 1;
+}
+
+.backgrid-paginator ul > li {
+  display: inline;
+}
+
+.backgrid-paginator ul > li > a,
+.backgrid-paginator ul > li > span {
+  float: left;
+  width: 30px;
+  height: 30px;
+  padding: 0;
+  line-height: 30px;
+  text-decoration: none;
+}
+
+.backgrid-paginator ul > li > a:hover,
+.backgrid-paginator ul > .active > a,
+.backgrid-paginator ul > .active > span {
+  background-color: #f5f5f5;
+}
+
+.backgrid-paginator ul > .active > a,
+.backgrid-paginator ul > .active > span {
+  color: #999999;
+  cursor: default;
+}
+
+.backgrid-paginator ul > .disabled > span,
+.backgrid-paginator ul > .disabled > a,
+.backgrid-paginator ul > .disabled > a:hover {
+  color: #999999;
+  cursor: default;
+}
\ No newline at end of file
diff --git a/web/pgadmin/static/css/backgrid/backgrid-paginator.min.css b/web/pgadmin/static/css/backgrid/backgrid-paginator.min.css
new file mode 100644
index 0000000..285fe81
--- /dev/null
+++ b/web/pgadmin/static/css/backgrid/backgrid-paginator.min.css
@@ -0,0 +1 @@
+.backgrid-paginator{text-align:center;border-top:0;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.backgrid-paginator ul{display:inline-block;*display:inline;margin:5px 0;*zoom:1}.backgrid-paginator ul>li{display:inline}.backgrid-paginator ul>li>a,.backgrid-paginator ul>li>span{float:left;width:30px;height:30px;padding:0;line-height:30px;text-decoration:none}.backgrid-paginator ul>li>a:hover,.backgrid-paginator ul>.active>a,.backgrid-paginator ul>.active>span{background-color:#f5f5f5}.backgrid-paginator ul>.active>a,.backgrid-paginator ul>.active>span{color:#999;cursor:default}.backgrid-paginator ul>.disabled>span,.backgrid-paginator ul>.disabled>a,.backgrid-paginator ul>.disabled>a:hover{color:#999;cursor:default}
\ No newline at end of file
diff --git a/web/pgadmin/static/css/backgrid/backgrid-select-all.css b/web/pgadmin/static/css/backgrid/backgrid-select-all.css
new file mode 100644
index 0000000..17471c1
--- /dev/null
+++ b/web/pgadmin/static/css/backgrid/backgrid-select-all.css
@@ -0,0 +1,12 @@
+/*
+  backgrid-select-all
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+
+.backgrid .select-row-cell,
+.backgrid .select-all-header-cell {
+  text-align: center;
+}
\ No newline at end of file
diff --git a/web/pgadmin/static/css/backgrid/backgrid-select-all.min.css b/web/pgadmin/static/css/backgrid/backgrid-select-all.min.css
new file mode 100644
index 0000000..41624eb
--- /dev/null
+++ b/web/pgadmin/static/css/backgrid/backgrid-select-all.min.css
@@ -0,0 +1 @@
+.backgrid .select-row-cell,.backgrid .select-all-header-cell{text-align:center}
\ No newline at end of file
diff --git a/web/pgadmin/static/js/backbone.paginator.js b/web/pgadmin/static/js/backbone.paginator.js
new file mode 100644
index 0000000..cc3de9b
--- /dev/null
+++ b/web/pgadmin/static/js/backbone.paginator.js
@@ -0,0 +1,1339 @@
+/*
+  backbone.paginator
+  http://github.com/backbone-paginator/backbone.paginator
+
+  Copyright (c) 2016 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+
+(function (factory) {
+
+  // CommonJS
+  if (typeof exports == "object" && typeof require == "function") {
+    module.exports = factory(require("underscore"), require("backbone"));
+  }
+  // AMD
+  else if (typeof define == "function" && define.amd) {
+    define(["underscore", "backbone"], factory);
+  }
+  // Browser
+  else if (typeof _ !== "undefined" && typeof Backbone !== "undefined") {
+    var oldPageableCollection = Backbone.PageableCollection;
+    var PageableCollection = factory(_, Backbone);
+
+    /**
+       __BROWSER ONLY__
+
+       If you already have an object named `PageableCollection` attached to the
+       `Backbone` module, you can use this to return a local reference to this
+       Backbone.PageableCollection class and reset the name
+       Backbone.PageableCollection to its previous definition.
+
+           // The left hand side gives you a reference to this
+           // Backbone.PageableCollection implementation, the right hand side
+           // resets Backbone.PageableCollection to your other
+           // Backbone.PageableCollection.
+           var PageableCollection = Backbone.PageableCollection.noConflict();
+
+       @static
+       @member Backbone.PageableCollection
+       @return {Backbone.PageableCollection}
+    */
+    Backbone.PageableCollection.noConflict = function () {
+      Backbone.PageableCollection = oldPageableCollection;
+      return PageableCollection;
+    };
+  }
+
+}(function (_, Backbone) {
+
+  "use strict";
+
+  var _extend = _.extend;
+  var _omit = _.omit;
+  var _clone = _.clone;
+  var _each = _.each;
+  var _pick = _.pick;
+  var _contains = _.contains;
+  var _isEmpty = _.isEmpty;
+  var _pairs = _.pairs;
+  var _invert = _.invert;
+  var _isArray = _.isArray;
+  var _isFunction = _.isFunction;
+  var _isObject = _.isObject;
+  var _keys = _.keys;
+  var _isUndefined = _.isUndefined;
+  var ceil = Math.ceil;
+  var floor = Math.floor;
+  var max = Math.max;
+
+  var BBColProto = Backbone.Collection.prototype;
+
+  function finiteInt (val, name) {
+    if (!_.isNumber(val) || _.isNaN(val) || !_.isFinite(val) || ~~val !== val) {
+      throw new TypeError("`" + name + "` must be a finite integer");
+    }
+    return val;
+  }
+
+  function queryStringToParams (qs) {
+    var kvp, k, v, ls, params = {}, decode = decodeURIComponent;
+    var kvps = qs.split('&');
+    for (var i = 0, l = kvps.length; i < l; i++) {
+      var param = kvps[i];
+      kvp = param.split('='), k = kvp[0], v = kvp[1];
+      if (v == null) v = true;
+      k = decode(k), v = decode(v), ls = params[k];
+      if (_isArray(ls)) ls.push(v);
+      else if (ls) params[k] = [ls, v];
+      else params[k] = v;
+    }
+    return params;
+  }
+
+  // hack to make sure the whatever event handlers for this event is run
+  // before func is, and the event handlers that func will trigger.
+  function runOnceAtLastHandler (col, event, func) {
+    var eventHandlers = col._events[event];
+    if (eventHandlers && eventHandlers.length) {
+      var lastHandler = eventHandlers[eventHandlers.length - 1];
+      var oldCallback = lastHandler.callback;
+      lastHandler.callback = function () {
+        try {
+          oldCallback.apply(this, arguments);
+          func();
+        }
+        catch (e) {
+          throw e;
+        }
+        finally {
+          lastHandler.callback = oldCallback;
+        }
+      };
+    }
+    else func();
+  }
+
+  var PARAM_TRIM_RE = /[\s'"]/g;
+  var URL_TRIM_RE = /[<>\s'"]/g;
+
+  /**
+     Drop-in replacement for Backbone.Collection. Supports server-side and
+     client-side pagination and sorting. Client-side mode also support fully
+     multi-directional synchronization of changes between pages.
+
+     @class Backbone.PageableCollection
+     @extends Backbone.Collection
+  */
+  var PageableCollection = Backbone.PageableCollection = Backbone.Collection.extend({
+
+    /**
+       The container object to store all pagination states.
+
+       You can override the default state by extending this class or specifying
+       them in an `options` hash to the constructor.
+
+       @property {Object} state
+
+       @property {0|1} [state.firstPage=1] The first page index. Set to 0 if
+       your server API uses 0-based indices. You should only override this value
+       during extension, initialization or reset by the server after
+       fetching. This value should be read only at other times.
+
+       @property {number} [state.lastPage=null] The last page index. This value
+       is __read only__ and it's calculated based on whether `firstPage` is 0 or
+       1, during bootstrapping, fetching and resetting. Please don't change this
+       value under any circumstances.
+
+       @property {number} [state.currentPage=null] The current page index. You
+       should only override this value during extension, initialization or reset
+       by the server after fetching. This value should be read only at other
+       times. Can be a 0-based or 1-based index, depending on whether
+       `firstPage` is 0 or 1. If left as default, it will be set to `firstPage`
+       on initialization.
+
+       @property {number} [state.pageSize=25] How many records to show per
+       page. This value is __read only__ after initialization, if you want to
+       change the page size after initialization, you must call #setPageSize.
+
+       @property {number} [state.totalPages=null] How many pages there are. This
+       value is __read only__ and it is calculated from `totalRecords`.
+
+       @property {number} [state.totalRecords=null] How many records there
+       are. This value is __required__ under server mode. This value is optional
+       for client mode as the number will be the same as the number of models
+       during bootstrapping and during fetching, either supplied by the server
+       in the metadata, or calculated from the size of the response.
+
+       @property {string} [state.sortKey=null] The model attribute to use for
+       sorting.
+
+       @property {-1|0|1} [state.order=-1] The order to use for sorting. Specify
+       -1 for ascending order or 1 for descending order. If 0, no client side
+       sorting will be done and the order query parameter will not be sent to
+       the server during a fetch.
+    */
+    state: {
+      firstPage: 1,
+      lastPage: null,
+      currentPage: null,
+      pageSize: 25,
+      totalPages: null,
+      totalRecords: null,
+      sortKey: null,
+      order: -1
+    },
+
+    /**
+       @property {"server"|"client"|"infinite"} [mode="server"] The mode of
+       operations for this collection. `"server"` paginates on the server-side,
+       `"client"` paginates on the client-side and `"infinite"` paginates on the
+       server-side for APIs that do not support `totalRecords`.
+    */
+    mode: "server",
+
+    /**
+       A translation map to convert Backbone.PageableCollection state attributes
+       to the query parameters accepted by your server API.
+
+       You can override the default state by extending this class or specifying
+       them in `options.queryParams` object hash to the constructor.
+
+       @property {Object} queryParams
+       @property {string} [queryParams.currentPage="page"]
+       @property {string} [queryParams.pageSize="per_page"]
+       @property {string} [queryParams.totalPages="total_pages"]
+       @property {string} [queryParams.totalRecords="total_entries"]
+       @property {string} [queryParams.sortKey="sort_by"]
+       @property {string} [queryParams.order="order"]
+       @property {string} [queryParams.directions={"-1": "asc", "1": "desc"}] A
+       map for translating a Backbone.PageableCollection#state.order constant to
+       the ones your server API accepts.
+    */
+    queryParams: {
+      currentPage: "page",
+      pageSize: "per_page",
+      totalPages: "total_pages",
+      totalRecords: "total_entries",
+      sortKey: "sort_by",
+      order: "order",
+      directions: {
+        "-1": "asc",
+        "1": "desc"
+      }
+    },
+
+    /**
+       __CLIENT MODE ONLY__
+
+       This collection is the internal storage for the bootstrapped or fetched
+       models. You can use this if you want to operate on all the pages.
+
+       @property {Backbone.Collection} fullCollection
+    */
+
+    /**
+       Given a list of models or model attributues, bootstraps the full
+       collection in client mode or infinite mode, or just the page you want in
+       server mode.
+
+       If you want to initialize a collection to a different state than the
+       default, you can specify them in `options.state`. Any state parameters
+       supplied will be merged with the default. If you want to change the
+       default mapping from #state keys to your server API's query parameter
+       names, you can specifiy an object hash in `option.queryParams`. Likewise,
+       any mapping provided will be merged with the default. Lastly, all
+       Backbone.Collection constructor options are also accepted.
+
+       See:
+
+       - Backbone.PageableCollection#state
+       - Backbone.PageableCollection#queryParams
+       - [Backbone.Collection#initialize](http://backbonejs.org/#Collection-constructor)
+
+       @param {Array.<Object>} [models]
+
+       @param {Object} [options]
+
+       @param {function(*, *): number} [options.comparator] If specified, this
+       comparator is set to the current page under server mode, or the #fullCollection
+       otherwise.
+
+       @param {boolean} [options.full] If `false` and either a
+       `options.comparator` or `sortKey` is defined, the comparator is attached
+       to the current page. Default is `true` under client or infinite mode and
+       the comparator will be attached to the #fullCollection.
+
+       @param {Object} [options.state] The state attributes overriding the defaults.
+
+       @param {string} [options.state.sortKey] The model attribute to use for
+       sorting. If specified instead of `options.comparator`, a comparator will
+       be automatically created using this value, and optionally a sorting order
+       specified in `options.state.order`. The comparator is then attached to
+       the new collection instance.
+
+       @param {-1|1} [options.state.order] The order to use for sorting. Specify
+       -1 for ascending order and 1 for descending order.
+
+       @param {Object} [options.queryParam]
+    */
+    constructor: function (models, options) {
+
+      BBColProto.constructor.apply(this, arguments);
+
+      options = options || {};
+
+      var mode = this.mode = options.mode || this.mode || PageableProto.mode;
+
+      var queryParams = _extend({}, PageableProto.queryParams, this.queryParams,
+                                options.queryParams || {});
+
+      queryParams.directions = _extend({},
+                                       PageableProto.queryParams.directions,
+                                       this.queryParams.directions,
+                                       queryParams.directions || {});
+
+      this.queryParams = queryParams;
+
+      var state = this.state = _extend({}, PageableProto.state, this.state,
+                                       options.state || {});
+
+      state.currentPage = state.currentPage == null ?
+        state.firstPage :
+        state.currentPage;
+
+      if (!_isArray(models)) models = models ? [models] : [];
+      models = models.slice();
+
+      if (mode != "server" && state.totalRecords == null && !_isEmpty(models)) {
+        state.totalRecords = models.length;
+      }
+
+      this.switchMode(mode, _extend({fetch: false,
+                                     resetState: false,
+                                     models: models}, options));
+
+      var comparator = options.comparator;
+
+      if (state.sortKey && !comparator) {
+        this.setSorting(state.sortKey, state.order, options);
+      }
+
+      if (mode != "server") {
+        var fullCollection = this.fullCollection;
+
+        if (comparator && options.full) {
+          this.comparator = null;
+          fullCollection.comparator = comparator;
+        }
+
+        if (options.full) fullCollection.sort();
+
+        // make sure the models in the current page and full collection have the
+        // same references
+        if (models && !_isEmpty(models)) {
+          this.reset(models, _extend({silent: true}, options));
+          this.getPage(state.currentPage);
+          models.splice.apply(models, [0, models.length].concat(this.models));
+        }
+      }
+
+      this._initState = _clone(this.state);
+    },
+
+    /**
+       Makes a Backbone.Collection that contains all the pages.
+
+       @private
+       @param {Array.<Object|Backbone.Model>} models
+       @param {Object} options Options for Backbone.Collection constructor.
+       @return {Backbone.Collection}
+    */
+    _makeFullCollection: function (models, options) {
+
+      var properties = ["url", "model", "sync", "comparator"];
+      var thisProto = this.constructor.prototype;
+      var i, length, prop;
+
+      var proto = {};
+      for (i = 0, length = properties.length; i < length; i++) {
+        prop = properties[i];
+        if (!_isUndefined(thisProto[prop])) {
+          proto[prop] = thisProto[prop];
+        }
+      }
+
+      var fullCollection = new (Backbone.Collection.extend(proto))(models, options);
+
+      for (i = 0, length = properties.length; i < length; i++) {
+        prop = properties[i];
+        if (this[prop] !== thisProto[prop]) {
+          fullCollection[prop] = this[prop];
+        }
+      }
+
+      return fullCollection;
+    },
+
+    /**
+       Factory method that returns a Backbone event handler that responses to
+       the `add`, `remove`, `reset`, and the `sort` events. The returned event
+       handler will synchronize the current page collection and the full
+       collection's models.
+
+       @private
+
+       @param {Backbone.PageableCollection} pageCol
+       @param {Backbone.Collection} fullCol
+
+       @return {function(string, Backbone.Model, Backbone.Collection, Object)}
+       Collection event handler
+    */
+    _makeCollectionEventHandler: function (pageCol, fullCol) {
+
+      return function collectionEventHandler (event, model, collection, options) {
+
+        var handlers = pageCol._handlers;
+        _each(_keys(handlers), function (event) {
+          var handler = handlers[event];
+          pageCol.off(event, handler);
+          fullCol.off(event, handler);
+        });
+
+        var state = _clone(pageCol.state);
+        var firstPage = state.firstPage;
+        var currentPage = firstPage === 0 ?
+          state.currentPage :
+          state.currentPage - 1;
+        var pageSize = state.pageSize;
+        var pageStart = currentPage * pageSize, pageEnd = pageStart + pageSize;
+
+        if (event == "add") {
+          var pageIndex, fullIndex, addAt, colToAdd, options = options || {};
+          if (collection == fullCol) {
+            fullIndex = fullCol.indexOf(model);
+            if (fullIndex >= pageStart && fullIndex < pageEnd) {
+              colToAdd = pageCol;
+              pageIndex = addAt = fullIndex - pageStart;
+            }
+          }
+          else {
+            pageIndex = pageCol.indexOf(model);
+            fullIndex = pageStart + pageIndex;
+            colToAdd = fullCol;
+            var addAt = !_isUndefined(options.at) ?
+              options.at + pageStart :
+              fullIndex;
+          }
+
+          if (!options.onRemove) {
+            ++state.totalRecords;
+            delete options.onRemove;
+          }
+
+          pageCol.state = pageCol._checkState(state);
+
+          if (colToAdd) {
+            colToAdd.add(model, _extend({}, options || {}, {at: addAt}));
+            var modelToRemove = pageIndex >= pageSize ?
+              model :
+              !_isUndefined(options.at) && addAt < pageEnd && pageCol.length > pageSize ?
+              pageCol.at(pageSize) :
+              null;
+            if (modelToRemove) {
+              runOnceAtLastHandler(collection, event, function () {
+                pageCol.remove(modelToRemove, {onAdd: true});
+              });
+            }
+          }
+        }
+
+        // remove the model from the other collection as well
+        if (event == "remove") {
+          if (!options.onAdd) {
+            // decrement totalRecords and update totalPages and lastPage
+            if (!--state.totalRecords) {
+              state.totalRecords = null;
+              state.totalPages = null;
+            }
+            else {
+              var totalPages = state.totalPages = ceil(state.totalRecords / pageSize);
+              state.lastPage = firstPage === 0 ? totalPages - 1 : totalPages || firstPage;
+              if (state.currentPage > totalPages) state.currentPage = state.lastPage;
+            }
+            pageCol.state = pageCol._checkState(state);
+
+            var nextModel, removedIndex = options.index;
+            if (collection == pageCol) {
+              if (nextModel = fullCol.at(pageEnd)) {
+                runOnceAtLastHandler(pageCol, event, function () {
+                  pageCol.push(nextModel, {onRemove: true});
+                });
+              }
+              else if (!pageCol.length && state.totalRecords) {
+                pageCol.reset(fullCol.models.slice(pageStart - pageSize, pageEnd - pageSize),
+                              _extend({}, options, {parse: false}));
+              }
+              fullCol.remove(model);
+            }
+            else if (removedIndex >= pageStart && removedIndex < pageEnd) {
+              if (nextModel = fullCol.at(pageEnd - 1)) {
+                runOnceAtLastHandler(pageCol, event, function() {
+                  pageCol.push(nextModel, {onRemove: true});
+                });
+              }
+              pageCol.remove(model);
+              if (!pageCol.length && state.totalRecords) {
+                pageCol.reset(fullCol.models.slice(pageStart - pageSize, pageEnd - pageSize),
+                              _extend({}, options, {parse: false}));
+              }
+            }
+          }
+          else delete options.onAdd;
+        }
+
+        if (event == "reset") {
+          options = collection;
+          collection = model;
+
+          // Reset that's not a result of getPage
+          if (collection == pageCol && options.from == null &&
+              options.to == null) {
+            var head = fullCol.models.slice(0, pageStart);
+            var tail = fullCol.models.slice(pageStart + pageCol.models.length);
+            fullCol.reset(head.concat(pageCol.models).concat(tail), options);
+          }
+          else if (collection == fullCol) {
+            if (!(state.totalRecords = fullCol.models.length)) {
+              state.totalRecords = null;
+              state.totalPages = null;
+            }
+            if (pageCol.mode == "client") {
+              firstPage = state.lastPage = state.currentPage = state.firstPage;
+              currentPage = firstPage === 0 ? state.currentPage : state.currentPage - 1;
+              pageStart = currentPage * pageSize;
+              pageEnd = pageStart + pageSize;
+            }
+            pageCol.state = pageCol._checkState(state);
+            pageCol.reset(fullCol.models.slice(pageStart, pageEnd),
+                          _extend({}, options, {parse: false}));
+          }
+        }
+
+        if (event == "sort") {
+          options = collection;
+          collection = model;
+          if (collection === fullCol) {
+            pageCol.reset(fullCol.models.slice(pageStart, pageEnd),
+                          _extend({}, options, {parse: false}));
+          }
+        }
+
+        _each(_keys(handlers), function (event) {
+          var handler = handlers[event];
+          _each([pageCol, fullCol], function (col) {
+            col.on(event, handler);
+            var callbacks = col._events[event] || [];
+            callbacks.unshift(callbacks.pop());
+          });
+        });
+      };
+    },
+
+    /**
+       Sanity check this collection's pagination states. Only perform checks
+       when all the required pagination state values are defined and not null.
+       If `totalPages` is undefined or null, it is set to `totalRecords` /
+       `pageSize`. `lastPage` is set according to whether `firstPage` is 0 or 1
+       when no error occurs.
+
+       @private
+
+       @throws {TypeError} If `totalRecords`, `pageSize`, `currentPage` or
+       `firstPage` is not a finite integer.
+
+       @throws {RangeError} If `pageSize`, `currentPage` or `firstPage` is out
+       of bounds.
+
+       @return {Object} Returns the `state` object if no error was found.
+    */
+    _checkState: function (state) {
+
+      var mode = this.mode;
+      var links = this.links;
+      var totalRecords = state.totalRecords;
+      var pageSize = state.pageSize;
+      var currentPage = state.currentPage;
+      var firstPage = state.firstPage;
+      var totalPages = state.totalPages;
+
+      if (totalRecords != null && pageSize != null && currentPage != null &&
+          firstPage != null && (mode == "infinite" ? links : true)) {
+
+        totalRecords = finiteInt(totalRecords, "totalRecords");
+        pageSize = finiteInt(pageSize, "pageSize");
+        currentPage = finiteInt(currentPage, "currentPage");
+        firstPage = finiteInt(firstPage, "firstPage");
+
+        if (pageSize < 1) {
+          throw new RangeError("`pageSize` must be >= 1");
+        }
+
+        totalPages = state.totalPages = ceil(totalRecords / pageSize);
+
+        if (firstPage < 0 || firstPage > 1) {
+          throw new RangeError("`firstPage must be 0 or 1`");
+        }
+
+        state.lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages || firstPage;
+
+        if (mode == "infinite") {
+          if (!links[currentPage + '']) {
+            throw new RangeError("No link found for page " + currentPage);
+          }
+        }
+        else if (currentPage < firstPage ||
+                 (totalPages > 0 &&
+                  (firstPage ? currentPage > totalPages : currentPage >= totalPages))) {
+          throw new RangeError("`currentPage` must be firstPage <= currentPage " +
+                               (firstPage ? "<" : "<=") +
+                               " totalPages if " + firstPage + "-based. Got " +
+                               currentPage + '.');
+        }
+      }
+
+      return state;
+    },
+
+    /**
+       Change the page size of this collection.
+
+       Under most if not all circumstances, you should call this method to
+       change the page size of a pageable collection because it will keep the
+       pagination state sane. By default, the method will recalculate the
+       current page number to one that will retain the current page's models
+       when increasing the page size. When decreasing the page size, this method
+       will retain the last models to the current page that will fit into the
+       smaller page size.
+
+       If `options.first` is true, changing the page size will also reset the
+       current page back to the first page instead of trying to be smart.
+
+       For server mode operations, changing the page size will trigger a #fetch
+       and subsequently a `reset` event.
+
+       For client mode operations, changing the page size will `reset` the
+       current page by recalculating the current page boundary on the client
+       side.
+
+       If `options.fetch` is true, a fetch can be forced if the collection is in
+       client mode.
+
+       @param {number} pageSize The new page size to set to #state.
+       @param {Object} [options] {@link #fetch} options.
+       @param {boolean} [options.first=false] Reset the current page number to
+       the first page if `true`.
+       @param {boolean} [options.fetch] If `true`, force a fetch in client mode.
+
+       @throws {TypeError} If `pageSize` is not a finite integer.
+       @throws {RangeError} If `pageSize` is less than 1.
+
+       @chainable
+       @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+       from fetch or this.
+    */
+    setPageSize: function (pageSize, options) {
+      pageSize = finiteInt(pageSize, "pageSize");
+
+      options = options || {first: false};
+
+      var state = this.state;
+      var totalPages = ceil(state.totalRecords / pageSize);
+      var currentPage = totalPages ?
+          max(state.firstPage, floor(totalPages * state.currentPage / state.totalPages)) :
+        state.firstPage;
+
+      state = this.state = this._checkState(_extend({}, state, {
+        pageSize: pageSize,
+        currentPage: options.first ? state.firstPage : currentPage,
+        totalPages: totalPages
+      }));
+
+      return this.getPage(state.currentPage, _omit(options, ["first"]));
+    },
+
+    /**
+       Switching between client, server and infinite mode.
+
+       If switching from client to server mode, the #fullCollection is emptied
+       first and then deleted and a fetch is immediately issued for the current
+       page from the server. Pass `false` to `options.fetch` to skip fetching.
+
+       If switching to infinite mode, and if `options.models` is given for an
+       array of models, #links will be populated with a URL per page, using the
+       default URL for this collection.
+
+       If switching from server to client mode, all of the pages are immediately
+       refetched. If you have too many pages, you can pass `false` to
+       `options.fetch` to skip fetching.
+
+       If switching to any mode from infinite mode, the #links will be deleted.
+
+       @param {"server"|"client"|"infinite"} [mode] The mode to switch to.
+
+       @param {Object} [options]
+
+       @param {boolean} [options.fetch=true] If `false`, no fetching is done.
+
+       @param {boolean} [options.resetState=true] If 'false', the state is not
+       reset, but checked for sanity instead.
+
+       @chainable
+       @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+       from fetch or this if `options.fetch` is `false`.
+    */
+    switchMode: function (mode, options) {
+
+      if (!_contains(["server", "client", "infinite"], mode)) {
+        throw new TypeError('`mode` must be one of "server", "client" or "infinite"');
+      }
+
+      options = options || {fetch: true, resetState: true};
+
+      var state = this.state = options.resetState ?
+        _clone(this._initState) :
+        this._checkState(_extend({}, this.state));
+
+      this.mode = mode;
+
+      var self = this;
+      var fullCollection = this.fullCollection;
+      var handlers = this._handlers = this._handlers || {}, handler;
+      if (mode != "server" && !fullCollection) {
+        fullCollection = this._makeFullCollection(options.models || [], options);
+        fullCollection.pageableCollection = this;
+        this.fullCollection = fullCollection;
+        var allHandler = this._makeCollectionEventHandler(this, fullCollection);
+        _each(["add", "remove", "reset", "sort"], function (event) {
+          handlers[event] = handler = _.bind(allHandler, {}, event);
+          self.on(event, handler);
+          fullCollection.on(event, handler);
+        });
+        fullCollection.comparator = this._fullComparator;
+      }
+      else if (mode == "server" && fullCollection) {
+        _each(_keys(handlers), function (event) {
+          handler = handlers[event];
+          self.off(event, handler);
+          fullCollection.off(event, handler);
+        });
+        delete this._handlers;
+        this._fullComparator = fullCollection.comparator;
+        delete this.fullCollection;
+      }
+
+      if (mode == "infinite") {
+        var links = this.links = {};
+        var firstPage = state.firstPage;
+        var totalPages = ceil(state.totalRecords / state.pageSize);
+        var lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages || firstPage;
+        for (var i = state.firstPage; i <= lastPage; i++) {
+          links[i] = this.url;
+        }
+      }
+      else if (this.links) delete this.links;
+
+      return options.fetch ?
+        this.fetch(_omit(options, "fetch", "resetState")) :
+        this;
+    },
+
+    /**
+       @return {boolean} `true` if this collection can page backward, `false`
+       otherwise.
+    */
+    hasPreviousPage: function () {
+      var state = this.state;
+      var currentPage = state.currentPage;
+      if (this.mode != "infinite") return currentPage > state.firstPage;
+      return !!this.links[currentPage - 1];
+    },
+
+    /**
+       @return {boolean} `true` if this collection can page forward, `false`
+       otherwise.
+    */
+    hasNextPage: function () {
+      var state = this.state;
+      var currentPage = this.state.currentPage;
+      if (this.mode != "infinite") return currentPage < state.lastPage;
+      return !!this.links[currentPage + 1];
+    },
+
+    /**
+       Fetch the first page in server mode, or reset the current page of this
+       collection to the first page in client or infinite mode.
+
+       @param {Object} options {@link #getPage} options.
+
+       @chainable
+       @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+       from fetch or this.
+    */
+    getFirstPage: function (options) {
+      return this.getPage("first", options);
+    },
+
+    /**
+       Fetch the previous page in server mode, or reset the current page of this
+       collection to the previous page in client or infinite mode.
+
+       @param {Object} options {@link #getPage} options.
+
+       @chainable
+       @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+       from fetch or this.
+    */
+    getPreviousPage: function (options) {
+      return this.getPage("prev", options);
+    },
+
+    /**
+       Fetch the next page in server mode, or reset the current page of this
+       collection to the next page in client mode.
+
+       @param {Object} options {@link #getPage} options.
+
+       @chainable
+       @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+       from fetch or this.
+    */
+    getNextPage: function (options) {
+      return this.getPage("next", options);
+    },
+
+    /**
+       Fetch the last page in server mode, or reset the current page of this
+       collection to the last page in client mode.
+
+       @param {Object} options {@link #getPage} options.
+
+       @chainable
+       @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+       from fetch or this.
+    */
+    getLastPage: function (options) {
+      return this.getPage("last", options);
+    },
+
+    /**
+       Given a page index, set #state.currentPage to that index. If this
+       collection is in server mode, fetch the page using the updated state,
+       otherwise, reset the current page of this collection to the page
+       specified by `index` in client mode. If `options.fetch` is true, a fetch
+       can be forced in client mode before resetting the current page. Under
+       infinite mode, if the index is less than the current page, a reset is
+       done as in client mode. If the index is greater than the current page
+       number, a fetch is made with the results **appended** to #fullCollection.
+       The current page will then be reset after fetching.
+
+       @param {number|string} index The page index to go to, or the page name to
+       look up from #links in infinite mode.
+       @param {Object} [options] {@link #fetch} options or
+       [reset](http://backbonejs.org/#Collection-reset) options for client mode
+       when `options.fetch` is `false`.
+       @param {boolean} [options.fetch=false] If true, force a {@link #fetch} in
+       client mode.
+
+       @throws {TypeError} If `index` is not a finite integer under server or
+       client mode, or does not yield a URL from #links under infinite mode.
+
+       @throws {RangeError} If `index` is out of bounds.
+
+       @chainable
+       @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+       from fetch or this.
+    */
+    getPage: function (index, options) {
+
+      var mode = this.mode, fullCollection = this.fullCollection;
+
+      options = options || {fetch: false};
+
+      var state = this.state,
+      firstPage = state.firstPage,
+      currentPage = state.currentPage,
+      lastPage = state.lastPage,
+      pageSize = state.pageSize;
+
+      var pageNum = index;
+      switch (index) {
+        case "first": pageNum = firstPage; break;
+        case "prev": pageNum = currentPage - 1; break;
+        case "next": pageNum = currentPage + 1; break;
+        case "last": pageNum = lastPage; break;
+        default: pageNum = finiteInt(index, "index");
+      }
+
+      this.state = this._checkState(_extend({}, state, {currentPage: pageNum}));
+
+      options.from = currentPage, options.to = pageNum;
+
+      var pageStart = (firstPage === 0 ? pageNum : pageNum - 1) * pageSize;
+      var pageModels = fullCollection && fullCollection.length ?
+        fullCollection.models.slice(pageStart, pageStart + pageSize) :
+        [];
+      if ((mode == "client" || (mode == "infinite" && !_isEmpty(pageModels))) &&
+          !options.fetch) {
+        this.reset(pageModels, _omit(options, "fetch"));
+        return this;
+      }
+
+      if (mode == "infinite") options.url = this.links[pageNum];
+
+      return this.fetch(_omit(options, "fetch"));
+    },
+
+    /**
+       Fetch the page for the provided item offset in server mode, or reset the current page of this
+       collection to the page for the provided item offset in client mode.
+
+       @param {Object} options {@link #getPage} options.
+
+       @chainable
+       @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+       from fetch or this.
+    */
+    getPageByOffset: function (offset, options) {
+      if (offset < 0) {
+        throw new RangeError("`offset must be > 0`");
+      }
+      offset = finiteInt(offset);
+
+      var page = floor(offset / this.state.pageSize);
+      if (this.state.firstPage !== 0) page++;
+      if (page > this.state.lastPage) page = this.state.lastPage;
+      return this.getPage(page, options);
+    },
+
+    /**
+       Overidden to make `getPage` compatible with Zepto.
+
+       @param {string} method
+       @param {Backbone.Model|Backbone.Collection} model
+       @param {Object} [options]
+
+       @return {XMLHttpRequest}
+    */
+    sync: function (method, model, options) {
+      var self = this;
+      if (self.mode == "infinite") {
+        var success = options.success;
+        var currentPage = self.state.currentPage;
+        options.success = function (resp, status, xhr) {
+          var links = self.links;
+          var newLinks = self.parseLinks(resp, _extend({xhr: xhr}, options));
+          if (newLinks.first) links[self.state.firstPage] = newLinks.first;
+          if (newLinks.prev) links[currentPage - 1] = newLinks.prev;
+          if (newLinks.next) links[currentPage + 1] = newLinks.next;
+          if (success) success(resp, status, xhr);
+        };
+      }
+
+      return (BBColProto.sync || Backbone.sync).call(self, method, model, options);
+    },
+
+    /**
+       Parse pagination links from the server response. Only valid under
+       infinite mode.
+
+       Given a response body and a XMLHttpRequest object, extract pagination
+       links from them for infinite paging.
+
+       This default implementation parses the RFC 5988 `Link` header and extract
+       3 links from it - `first`, `prev`, `next`. Any subclasses overriding this
+       method __must__ return an object hash having only the keys
+       above. However, simply returning a `next` link or an empty hash if there
+       are no more links should be enough for most implementations.
+
+       @param {*} resp The deserialized response body.
+       @param {Object} [options]
+       @param {XMLHttpRequest} [options.xhr] The XMLHttpRequest object for this
+       response.
+       @return {Object}
+    */
+    parseLinks: function (resp, options) {
+      var links = {};
+      var linkHeader = options.xhr.getResponseHeader("Link");
+      if (linkHeader) {
+        var relations = ["first", "prev", "next"];
+        _each(linkHeader.split(","), function (linkValue) {
+          var linkParts = linkValue.split(";");
+          var url = linkParts[0].replace(URL_TRIM_RE, '');
+          var params = linkParts.slice(1);
+          _each(params, function (param) {
+            var paramParts = param.split("=");
+            var key = paramParts[0].replace(PARAM_TRIM_RE, '');
+            var value = paramParts[1].replace(PARAM_TRIM_RE, '');
+            if (key == "rel" && _contains(relations, value)) links[value] = url;
+          });
+        });
+      }
+
+      return links;
+    },
+
+    /**
+       Parse server response data.
+
+       This default implementation assumes the response data is in one of two
+       structures:
+
+           [
+             {}, // Your new pagination state
+             [{}, ...] // An array of JSON objects
+           ]
+
+       Or,
+
+           [{}] // An array of JSON objects
+
+       The first structure is the preferred form because the pagination states
+       may have been updated on the server side, sending them down again allows
+       this collection to update its states. If the response has a pagination
+       state object, it is checked for errors.
+
+       The second structure is the
+       [Backbone.Collection#parse](http://backbonejs.org/#Collection-parse)
+       default.
+
+       **Note:** this method has been further simplified since 1.1.7. While
+       existing #parse implementations will continue to work, new code is
+       encouraged to override #parseState and #parseRecords instead.
+
+       @param {Object} resp The deserialized response data from the server.
+       @param {Object} the options for the ajax request
+
+       @return {Array.<Object>} An array of model objects
+    */
+    parse: function (resp, options) {
+      var newState = this.parseState(resp, _clone(this.queryParams), _clone(this.state), options);
+      if (newState) this.state = this._checkState(_extend({}, this.state, newState));
+      return this.parseRecords(resp, options);
+    },
+
+    /**
+       Parse server response for server pagination state updates. Not applicable
+       under infinite mode.
+
+       This default implementation first checks whether the response has any
+       state object as documented in #parse. If it exists, a state object is
+       returned by mapping the server state keys to this pageable collection
+       instance's query parameter keys using `queryParams`.
+
+       It is __NOT__ neccessary to return a full state object complete with all
+       the mappings defined in #queryParams. Any state object resulted is merged
+       with a copy of the current pageable collection state and checked for
+       sanity before actually updating. Most of the time, simply providing a new
+       `totalRecords` value is enough to trigger a full pagination state
+       recalculation.
+
+           parseState: function (resp, queryParams, state, options) {
+             return {totalRecords: resp.total_entries};
+           }
+
+       If you want to use header fields use:
+
+           parseState: function (resp, queryParams, state, options) {
+               return {totalRecords: options.xhr.getResponseHeader("X-total")};
+           }
+
+       This method __MUST__ return a new state object instead of directly
+       modifying the #state object. The behavior of directly modifying #state is
+       undefined.
+
+       @param {Object} resp The deserialized response data from the server.
+       @param {Object} queryParams A copy of #queryParams.
+       @param {Object} state A copy of #state.
+       @param {Object} [options] The options passed through from
+       `parse`. (backbone >= 0.9.10 only)
+
+       @return {Object} A new (partial) state object.
+     */
+    parseState: function (resp, queryParams, state, options) {
+      if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
+
+        var newState = _clone(state);
+        var serverState = resp[0];
+
+        _each(_pairs(_omit(queryParams, "directions")), function (kvp) {
+          var k = kvp[0], v = kvp[1];
+          var serverVal = serverState[v];
+          if (!_isUndefined(serverVal) && !_.isNull(serverVal)) newState[k] = serverState[v];
+        });
+
+        if (serverState.order) {
+          newState.order = _invert(queryParams.directions)[serverState.order] * 1;
+        }
+
+        return newState;
+      }
+    },
+
+    /**
+       Parse server response for an array of model objects.
+
+       This default implementation first checks whether the response has any
+       state object as documented in #parse. If it exists, the array of model
+       objects is assumed to be the second element, otherwise the entire
+       response is returned directly.
+
+       @param {Object} resp The deserialized response data from the server.
+       @param {Object} [options] The options passed through from the
+       `parse`. (backbone >= 0.9.10 only)
+
+       @return {Array.<Object>} An array of model objects
+     */
+    parseRecords: function (resp, options) {
+      if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
+        return resp[1];
+      }
+
+      return resp;
+    },
+
+    /**
+       Fetch a page from the server in server mode, or all the pages in client
+       mode. Under infinite mode, the current page is refetched by default and
+       then reset.
+
+       The query string is constructed by translating the current pagination
+       state to your server API query parameter using #queryParams. The current
+       page will reset after fetch.
+
+       @param {Object} [options] Accepts all
+       [Backbone.Collection#fetch](http://backbonejs.org/#Collection-fetch)
+       options.
+
+       @return {XMLHttpRequest}
+    */
+    fetch: function (options) {
+
+      options = options || {};
+
+      var state = this._checkState(this.state);
+
+      var mode = this.mode;
+
+      if (mode == "infinite" && !options.url) {
+        options.url = this.links[state.currentPage];
+      }
+
+      var data = options.data || {};
+
+      // dedup query params
+      var url = options.url || this.url || "";
+      if (_isFunction(url)) url = url.call(this);
+      var qsi = url.indexOf('?');
+      if (qsi != -1) {
+        _extend(data, queryStringToParams(url.slice(qsi + 1)));
+        url = url.slice(0, qsi);
+      }
+
+      options.url = url;
+      options.data = data;
+
+      // map params except directions
+      var queryParams = this.mode == "client" ?
+        _pick(this.queryParams, "sortKey", "order") :
+        _omit(_pick(this.queryParams, _keys(PageableProto.queryParams)),
+              "directions");
+
+      var thisCopy = _.clone(this);
+      _.each(queryParams, function (v, k) {
+        v = _isFunction(v) ? v.call(thisCopy) : v;
+        if (state[k] != null && v != null && _.isUndefined(data[v])) {
+          data[v] = state[k];
+        }
+      }, this);
+
+      // fix up sorting parameters
+      var i;
+      if (state.sortKey && state.order) {
+        var o = _isFunction(queryParams.order) ?
+          queryParams.order.call(thisCopy) :
+          queryParams.order;
+          if (!_isArray(state.order)) {
+              data[o] = this.queryParams.directions[state.order + ""];
+          }
+          else {
+              data[o] = [];
+              for (i = 0; i < state.order.length; i += 1) {
+                  data[o].push(this.queryParams.directions[state.order[i]]);
+              }
+          }
+      }
+      else if (!state.sortKey) delete data[queryParams.order];
+
+      // map extra query parameters
+      var extraKvps = _pairs(_omit(this.queryParams,
+                                   _keys(PageableProto.queryParams))),
+          kvp,
+          v;
+      for (i = 0; i < extraKvps.length; i++) {
+        kvp = extraKvps[i];
+        v = kvp[1];
+        v = _isFunction(v) ? v.call(thisCopy) : v;
+        if (v != null) data[kvp[0]] = v;
+      }
+
+      if (mode != "server") {
+        var self = this, fullCol = this.fullCollection;
+        var success = options.success;
+        options.success = function (col, resp, opts) {
+
+          // make sure the caller's intent is obeyed
+          opts = opts || {};
+          if (_isUndefined(options.silent)) delete opts.silent;
+          else opts.silent = options.silent;
+
+          var models = col.models;
+          if (mode == "client") fullCol.reset(models, opts);
+          else {
+            fullCol.add(models, _extend({at: fullCol.length},
+                                        _extend(opts, {parse: false})));
+            self.trigger("reset", self, opts);
+          }
+
+          if (success) success(col, resp, opts);
+        };
+
+        // silent the first reset from backbone
+        return BBColProto.fetch.call(this, _extend({}, options, {silent: true}));
+      }
+
+      return BBColProto.fetch.call(this, options);
+    },
+
+    /**
+       Convenient method for making a `comparator` sorted by a model attribute
+       identified by `sortKey` and ordered by `order`.
+
+       Like a Backbone.Collection, a Backbone.PageableCollection will maintain
+       the __current page__ in sorted order on the client side if a `comparator`
+       is attached to it. If the collection is in client mode, you can attach a
+       comparator to #fullCollection to have all the pages reflect the global
+       sorting order by specifying an option `full` to `true`. You __must__ call
+       `sort` manually or #fullCollection.sort after calling this method to
+       force a resort.
+
+       While you can use this method to sort the current page in server mode,
+       the sorting order may not reflect the global sorting order due to the
+       additions or removals of the records on the server since the last
+       fetch. If you want the most updated page in a global sorting order, it is
+       recommended that you set #state.sortKey and optionally #state.order, and
+       then call #fetch.
+
+       @protected
+
+       @param {string} [sortKey=this.state.sortKey] See `state.sortKey`.
+       @param {number} [order=this.state.order] See `state.order`.
+       @param {(function(Backbone.Model, string): Object) | string} [sortValue] See #setSorting.
+
+       See [Backbone.Collection.comparator](http://backbonejs.org/#Collection-comparator).
+    */
+    _makeComparator: function (sortKey, order, sortValue) {
+      var state = this.state;
+
+      sortKey = sortKey || state.sortKey;
+      order = order || state.order;
+
+      if (!sortKey || !order) return;
+
+      if (!sortValue) sortValue = function (model, attr) {
+        return model.get(attr);
+      };
+
+      return function (left, right) {
+        var l = sortValue(left, sortKey), r = sortValue(right, sortKey), t;
+        if (order === 1) t = l, l = r, r = t;
+        if (l === r) return 0;
+        else if (l < r) return -1;
+        return 1;
+      };
+    },
+
+    /**
+       Adjusts the sorting for this pageable collection.
+
+       Given a `sortKey` and an `order`, sets `state.sortKey` and
+       `state.order`. A comparator can be applied on the client side to sort in
+       the order defined if `options.side` is `"client"`. By default the
+       comparator is applied to the #fullCollection. Set `options.full` to
+       `false` to apply a comparator to the current page under any mode. Setting
+       `sortKey` to `null` removes the comparator from both the current page and
+       the full collection.
+
+       If a `sortValue` function is given, it will be passed the `(model,
+       sortKey)` arguments and is used to extract a value from the model during
+       comparison sorts. If `sortValue` is not given, `model.get(sortKey)` is
+       used for sorting.
+
+       @chainable
+
+       @param {string} sortKey See `state.sortKey`.
+       @param {number} [order=this.state.order] See `state.order`.
+       @param {Object} [options]
+       @param {"server"|"client"} [options.side] By default, `"client"` if
+       `mode` is `"client"`, `"server"` otherwise.
+       @param {boolean} [options.full=true]
+       @param {(function(Backbone.Model, string): Object) | string} [options.sortValue]
+    */
+    setSorting: function (sortKey, order, options) {
+
+      var state = this.state;
+
+      state.sortKey = sortKey;
+      state.order = order = order || state.order;
+
+      var fullCollection = this.fullCollection;
+
+      var delComp = false, delFullComp = false;
+
+      if (!sortKey) delComp = delFullComp = true;
+
+      var mode = this.mode;
+      options = _extend({side: mode == "client" ? mode : "server", full: true},
+                        options);
+
+      var comparator = this._makeComparator(sortKey, order, options.sortValue);
+
+      var full = options.full, side = options.side;
+
+      if (side == "client") {
+        if (full) {
+          if (fullCollection) fullCollection.comparator = comparator;
+          delComp = true;
+        }
+        else {
+          this.comparator = comparator;
+          delFullComp = true;
+        }
+      }
+      else if (side == "server" && !full) {
+        this.comparator = comparator;
+      }
+
+      if (delComp) this.comparator = null;
+      if (delFullComp && fullCollection) fullCollection.comparator = null;
+
+      return this;
+    }
+
+  });
+
+  var PageableProto = PageableCollection.prototype;
+
+  return PageableCollection;
+
+}));
diff --git a/web/pgadmin/static/js/backbone.paginator.min.js b/web/pgadmin/static/js/backbone.paginator.min.js
new file mode 100644
index 0000000..57b9f42
--- /dev/null
+++ b/web/pgadmin/static/js/backbone.paginator.min.js
@@ -0,0 +1,8 @@
+/*
+  backbone.paginator
+  http://github.com/backbone-paginator/backbone.paginator
+
+  Copyright (c) 2016 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+!function(a){if("object"==typeof exports&&"function"==typeof require)module.exports=a(require("underscore"),require("backbone"));else if("function"==typeof define&&define.amd)define(["underscore","backbone"],a);else if("undefined"!=typeof _&&"undefined"!=typeof Backbone){var b=Backbone.PageableCollection,c=a(_,Backbone);Backbone.PageableCollection.noConflict=function(){return Backbone.PageableCollection=b,c}}}(function(a,b){"use strict";function c(b,c){if(!a.isNumber(b)||a.isNaN(b)||!a.isFinite(b)||~~b!==b)throw new TypeError("`"+c+"` must be a finite integer");return b}function d(a){for(var b,c,d,e,f={},g=decodeURIComponent,h=a.split("&"),i=0,j=h.length;j>i;i++){var k=h[i];b=k.split("="),c=b[0],d=b[1],null==d&&(d=!0),c=g(c),d=g(d),e=f[c],o(e)?e.push(d):e?f[c]=[e,d]:f[c]=d}return f}function e(a,b,c){var d=a._events[b];if(d&&d.length){var e=d[d.length-1],f=e.callback;e.callback=function(){try{f.apply(this,arguments),c()}catch(a){throw a}finally{e.callback=f}}}else c()}var f=a.extend,g=a.omit,h=a.clone,i=a.each,j=a.pick,k=a.contains,l=a.isEmpty,m=a.pairs,n=a.invert,o=a.isArray,p=a.isFunction,q=a.isObject,r=a.keys,s=a.isUndefined,t=Math.ceil,u=Math.floor,v=Math.max,w=b.Collection.prototype,x=/[\s'"]/g,y=/[<>\s'"]/g,z=b.PageableCollection=b.Collection.extend({state:{firstPage:1,lastPage:null,currentPage:null,pageSize:25,totalPages:null,totalRecords:null,sortKey:null,order:-1},mode:"server",queryParams:{currentPage:"page",pageSize:"per_page",totalPages:"total_pages",totalRecords:"total_entries",sortKey:"sort_by",order:"order",directions:{"-1":"asc",1:"desc"}},constructor:function(a,b){w.constructor.apply(this,arguments),b=b||{};var c=this.mode=b.mode||this.mode||A.mode,d=f({},A.queryParams,this.queryParams,b.queryParams||{});d.directions=f({},A.queryParams.directions,this.queryParams.directions,d.directions||{}),this.queryParams=d;var e=this.state=f({},A.state,this.state,b.state||{});e.currentPage=null==e.currentPage?e.firstPage:e.currentPage,o(a)||(a=a?[a]:[]),a=a.slice(),"server"==c||null!=e.totalRecords||l(a)||(e.totalRecords=a.length),this.switchMode(c,f({fetch:!1,resetState:!1,models:a},b));var g=b.comparator;if(e.sortKey&&!g&&this.setSorting(e.sortKey,e.order,b),"server"!=c){var i=this.fullCollection;g&&b.full&&(this.comparator=null,i.comparator=g),b.full&&i.sort(),a&&!l(a)&&(this.reset(a,f({silent:!0},b)),this.getPage(e.currentPage),a.splice.apply(a,[0,a.length].concat(this.models)))}this._initState=h(this.state)},_makeFullCollection:function(a,c){var d,e,f,g=["url","model","sync","comparator"],h=this.constructor.prototype,i={};for(d=0,e=g.length;e>d;d++)f=g[d],s(h[f])||(i[f]=h[f]);var j=new(b.Collection.extend(i))(a,c);for(d=0,e=g.length;e>d;d++)f=g[d],this[f]!==h[f]&&(j[f]=this[f]);return j},_makeCollectionEventHandler:function(a,b){return function(c,d,g,j){var k=a._handlers;i(r(k),function(c){var d=k[c];a.off(c,d),b.off(c,d)});var l=h(a.state),m=l.firstPage,n=0===m?l.currentPage:l.currentPage-1,o=l.pageSize,p=n*o,q=p+o;if("add"==c){var u,v,w,x,j=j||{};if(g==b)v=b.indexOf(d),v>=p&&q>v&&(x=a,u=w=v-p);else{u=a.indexOf(d),v=p+u,x=b;var w=s(j.at)?v:j.at+p}if(j.onRemove||(++l.totalRecords,delete j.onRemove),a.state=a._checkState(l),x){x.add(d,f({},j||{},{at:w}));var y=u>=o?d:!s(j.at)&&q>w&&a.length>o?a.at(o):null;y&&e(g,c,function(){a.remove(y,{onAdd:!0})})}}if("remove"==c)if(j.onAdd)delete j.onAdd;else{if(--l.totalRecords){var z=l.totalPages=t(l.totalRecords/o);l.lastPage=0===m?z-1:z||m,l.currentPage>z&&(l.currentPage=l.lastPage)}else l.totalRecords=null,l.totalPages=null;a.state=a._checkState(l);var A,B=j.index;g==a?((A=b.at(q))?e(a,c,function(){a.push(A,{onRemove:!0})}):!a.length&&l.totalRecords&&a.reset(b.models.slice(p-o,q-o),f({},j,{parse:!1})),b.remove(d)):B>=p&&q>B&&((A=b.at(q-1))&&e(a,c,function(){a.push(A,{onRemove:!0})}),a.remove(d),!a.length&&l.totalRecords&&a.reset(b.models.slice(p-o,q-o),f({},j,{parse:!1})))}if("reset"==c)if(j=g,g=d,g==a&&null==j.from&&null==j.to){var C=b.models.slice(0,p),D=b.models.slice(p+a.models.length);b.reset(C.concat(a.models).concat(D),j)}else g==b&&((l.totalRecords=b.models.length)||(l.totalRecords=null,l.totalPages=null),"client"==a.mode&&(m=l.lastPage=l.currentPage=l.firstPage,n=0===m?l.currentPage:l.currentPage-1,p=n*o,q=p+o),a.state=a._checkState(l),a.reset(b.models.slice(p,q),f({},j,{parse:!1})));"sort"==c&&(j=g,g=d,g===b&&a.reset(b.models.slice(p,q),f({},j,{parse:!1}))),i(r(k),function(c){var d=k[c];i([a,b],function(a){a.on(c,d);var b=a._events[c]||[];b.unshift(b.pop())})})}},_checkState:function(a){var b=this.mode,d=this.links,e=a.totalRecords,f=a.pageSize,g=a.currentPage,h=a.firstPage,i=a.totalPages;if(null!=e&&null!=f&&null!=g&&null!=h&&("infinite"==b?d:!0)){if(e=c(e,"totalRecords"),f=c(f,"pageSize"),g=c(g,"currentPage"),h=c(h,"firstPage"),1>f)throw new RangeError("`pageSize` must be >= 1");if(i=a.totalPages=t(e/f),0>h||h>1)throw new RangeError("`firstPage must be 0 or 1`");if(a.lastPage=0===h?v(0,i-1):i||h,"infinite"==b){if(!d[g+""])throw new RangeError("No link found for page "+g)}else if(h>g||i>0&&(h?g>i:g>=i))throw new RangeError("`currentPage` must be firstPage <= currentPage "+(h?"<":"<=")+" totalPages if "+h+"-based. Got "+g+".")}return a},setPageSize:function(a,b){a=c(a,"pageSize"),b=b||{first:!1};var d=this.state,e=t(d.totalRecords/a),h=e?v(d.firstPage,u(e*d.currentPage/d.totalPages)):d.firstPage;return d=this.state=this._checkState(f({},d,{pageSize:a,currentPage:b.first?d.firstPage:h,totalPages:e})),this.getPage(d.currentPage,g(b,["first"]))},switchMode:function(b,c){if(!k(["server","client","infinite"],b))throw new TypeError('`mode` must be one of "server", "client" or "infinite"');c=c||{fetch:!0,resetState:!0};var d=this.state=c.resetState?h(this._initState):this._checkState(f({},this.state));this.mode=b;var e,j=this,l=this.fullCollection,m=this._handlers=this._handlers||{};if("server"==b||l)"server"==b&&l&&(i(r(m),function(a){e=m[a],j.off(a,e),l.off(a,e)}),delete this._handlers,this._fullComparator=l.comparator,delete this.fullCollection);else{l=this._makeFullCollection(c.models||[],c),l.pageableCollection=this,this.fullCollection=l;var n=this._makeCollectionEventHandler(this,l);i(["add","remove","reset","sort"],function(b){m[b]=e=a.bind(n,{},b),j.on(b,e),l.on(b,e)}),l.comparator=this._fullComparator}if("infinite"==b)for(var o=this.links={},p=d.firstPage,q=t(d.totalRecords/d.pageSize),s=0===p?v(0,q-1):q||p,u=d.firstPage;s>=u;u++)o[u]=this.url;else this.links&&delete this.links;return c.fetch?this.fetch(g(c,"fetch","resetState")):this},hasPreviousPage:function(){var a=this.state,b=a.currentPage;return"infinite"!=this.mode?b>a.firstPage:!!this.links[b-1]},hasNextPage:function(){var a=this.state,b=this.state.currentPage;return"infinite"!=this.mode?b<a.lastPage:!!this.links[b+1]},getFirstPage:function(a){return this.getPage("first",a)},getPreviousPage:function(a){return this.getPage("prev",a)},getNextPage:function(a){return this.getPage("next",a)},getLastPage:function(a){return this.getPage("last",a)},getPage:function(a,b){var d=this.mode,e=this.fullCollection;b=b||{fetch:!1};var h=this.state,i=h.firstPage,j=h.currentPage,k=h.lastPage,m=h.pageSize,n=a;switch(a){case"first":n=i;break;case"prev":n=j-1;break;case"next":n=j+1;break;case"last":n=k;break;default:n=c(a,"index")}this.state=this._checkState(f({},h,{currentPage:n})),b.from=j,b.to=n;var o=(0===i?n:n-1)*m,p=e&&e.length?e.models.slice(o,o+m):[];return"client"!=d&&("infinite"!=d||l(p))||b.fetch?("infinite"==d&&(b.url=this.links[n]),this.fetch(g(b,"fetch"))):(this.reset(p,g(b,"fetch")),this)},getPageByOffset:function(a,b){if(0>a)throw new RangeError("`offset must be > 0`");a=c(a);var d=u(a/this.state.pageSize);return 0!==this.state.firstPage&&d++,d>this.state.lastPage&&(d=this.state.lastPage),this.getPage(d,b)},sync:function(a,c,d){var e=this;if("infinite"==e.mode){var g=d.success,h=e.state.currentPage;d.success=function(a,b,c){var i=e.links,j=e.parseLinks(a,f({xhr:c},d));j.first&&(i[e.state.firstPage]=j.first),j.prev&&(i[h-1]=j.prev),j.next&&(i[h+1]=j.next),g&&g(a,b,c)}}return(w.sync||b.sync).call(e,a,c,d)},parseLinks:function(a,b){var c={},d=b.xhr.getResponseHeader("Link");if(d){var e=["first","prev","next"];i(d.split(","),function(a){var b=a.split(";"),d=b[0].replace(y,""),f=b.slice(1);i(f,function(a){var b=a.split("="),f=b[0].replace(x,""),g=b[1].replace(x,"");"rel"==f&&k(e,g)&&(c[g]=d)})})}return c},parse:function(a,b){var c=this.parseState(a,h(this.queryParams),h(this.state),b);return c&&(this.state=this._checkState(f({},this.state,c))),this.parseRecords(a,b)},parseState:function(b,c,d,e){if(b&&2===b.length&&q(b[0])&&o(b[1])){var f=h(d),j=b[0];return i(m(g(c,"directions")),function(b){var c=b[0],d=b[1],e=j[d];s(e)||a.isNull(e)||(f[c]=j[d])}),j.order&&(f.order=1*n(c.directions)[j.order]),f}},parseRecords:function(a,b){return a&&2===a.length&&q(a[0])&&o(a[1])?a[1]:a},fetch:function(b){b=b||{};var c=this._checkState(this.state),e=this.mode;"infinite"!=e||b.url||(b.url=this.links[c.currentPage]);var h=b.data||{},i=b.url||this.url||"";p(i)&&(i=i.call(this));var k=i.indexOf("?");-1!=k&&(f(h,d(i.slice(k+1))),i=i.slice(0,k)),b.url=i,b.data=h;var l="client"==this.mode?j(this.queryParams,"sortKey","order"):g(j(this.queryParams,r(A.queryParams)),"directions"),n=a.clone(this);a.each(l,function(b,d){b=p(b)?b.call(n):b,null!=c[d]&&null!=b&&a.isUndefined(h[b])&&(h[b]=c[d])},this);var q;if(c.sortKey&&c.order){var t=p(l.order)?l.order.call(n):l.order;if(o(c.order))for(h[t]=[],q=0;q<c.order.length;q+=1)h[t].push(this.queryParams.directions[c.order[q]]);else h[t]=this.queryParams.directions[c.order+""]}else c.sortKey||delete h[l.order];var u,v,x=m(g(this.queryParams,r(A.queryParams)));for(q=0;q<x.length;q++)u=x[q],v=u[1],v=p(v)?v.call(n):v,null!=v&&(h[u[0]]=v);if("server"!=e){var y=this,z=this.fullCollection,B=b.success;return b.success=function(a,c,d){d=d||{},s(b.silent)?delete d.silent:d.silent=b.silent;var g=a.models;"client"==e?z.reset(g,d):(z.add(g,f({at:z.length},f(d,{parse:!1}))),y.trigger("reset",y,d)),B&&B(a,c,d)},w.fetch.call(this,f({},b,{silent:!0}))}return w.fetch.call(this,b)},_makeComparator:function(a,b,c){var d=this.state;return a=a||d.sortKey,b=b||d.order,a&&b?(c||(c=function(a,b){return a.get(b)}),function(d,e){var f,g=c(d,a),h=c(e,a);return 1===b&&(f=g,g=h,h=f),g===h?0:h>g?-1:1}):void 0},setSorting:function(a,b,c){var d=this.state;d.sortKey=a,d.order=b=b||d.order;var e=this.fullCollection,g=!1,h=!1;a||(g=h=!0);var i=this.mode;c=f({side:"client"==i?i:"server",full:!0},c);var j=this._makeComparator(a,b,c.sortValue),k=c.full,l=c.side;return"client"==l?k?(e&&(e.comparator=j),g=!0):(this.comparator=j,h=!0):"server"!=l||k||(this.comparator=j),g&&(this.comparator=null),h&&e&&(e.comparator=null),this}}),A=z.prototype;return z});
\ No newline at end of file
diff --git a/web/pgadmin/static/js/backgrid/backgrid-filter.js b/web/pgadmin/static/js/backgrid/backgrid-filter.js
new file mode 100644
index 0000000..c4d94af
--- /dev/null
+++ b/web/pgadmin/static/js/backgrid/backgrid-filter.js
@@ -0,0 +1,512 @@
+/*
+  backgrid-filter
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+(function (root, factory) {
+
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(["underscore", "backbone", "backgrid"], factory);
+  } else if (typeof exports == "object") {
+    // CommonJS
+    (function () {
+      var lunr;
+      try { lunr = require("lunr"); } catch (e) {}
+      module.exports = factory(require("underscore"),
+                               require("backbone"),
+                               require("backgrid"),
+                               lunr);
+    }());
+  } else {
+    // Browser
+    factory(root._, root.Backbone, root.Backgrid, root.lunr);
+  }
+
+}(this, function (_, Backbone, Backgrid, lunr) {
+
+  "use strict";
+
+  /**
+     ServerSideFilter is a search form widget that submits a query to the server
+     for filtering the current collection.
+
+     @class Backgrid.Extension.ServerSideFilter
+  */
+  var ServerSideFilter = Backgrid.Extension.ServerSideFilter = Backbone.View.extend({
+
+    /** @property */
+    tagName: "form",
+
+    /** @property */
+    className: "backgrid-filter form-search",
+
+    /** @property {function(Object, ?Object=): string} template */
+    template: function (data) {
+      return '<span class="search">&nbsp;</span><input type="search" ' + (data.placeholder ? 'placeholder="' + data.placeholder + '"' : '') + ' name="' + data.name + '" ' + (data.value ? 'value="' + data.value + '"' : '') + '/><a class="clear" data-backgrid-action="clear" href="#">&times;</a>';
+    },
+
+    /** @property */
+    events: {
+      "keyup input[type=search]": "showClearButtonMaybe",
+      "click a[data-backgrid-action=clear]": "clear",
+      "submit": "search"
+    },
+
+    /** @property {string} [name='q'] Query key */
+    name: "q",
+
+    /** @property {string} [value] The search box value.  */
+    value: null,
+
+    /**
+       @property {string} [placeholder] The HTML5 placeholder to appear beneath
+       the search box.
+    */
+    placeholder: null,
+
+    /**
+       @param {Object} options
+       @param {Backbone.Collection} options.collection
+       @param {string} [options.name]
+       @param {string} [options.value]
+       @param {string} [options.placeholder]
+       @param {function(Object): string} [options.template]
+    */
+    initialize: function (options) {
+      ServerSideFilter.__super__.initialize.apply(this, arguments);
+      this.name = options.name || this.name;
+      this.value = options.value || this.value;
+      this.placeholder = options.placeholder || this.placeholder;
+      this.template = options.template || this.template;
+
+      // Persist the query on pagination
+      var collection = this.collection, self = this;
+      if (Backbone.PageableCollection &&
+          collection instanceof Backbone.PageableCollection &&
+          collection.mode == "server") {
+        collection.queryParams[this.name] = function () {
+          return self.searchBox().val() || null;
+        };
+      }
+    },
+
+    /**
+       Event handler. Clear the search box and reset the internal search value.
+     */
+    clearSearchBox: function() {
+      this.value = null;
+      this.searchBox().val(null);
+      this.showClearButtonMaybe();
+    },
+
+    /**
+       Event handler. Show the clear button when the search box has text, hide
+       it otherwise.
+     */
+    showClearButtonMaybe: function () {
+      var $clearButton = this.clearButton();
+      var searchTerms = this.searchBox().val();
+      if (searchTerms) $clearButton.show();
+      else $clearButton.hide();
+    },
+
+    /**
+       Returns the search input box.
+     */
+    searchBox: function () {
+      return this.$el.find("input[type=search]");
+    },
+
+    /**
+       Returns the clear button.
+     */
+    clearButton: function () {
+      return this.$el.find("a[data-backgrid-action=clear]");
+    },
+
+
+    /**
+       Returns the current search query.
+     */
+    query: function() {
+      this.value = this.searchBox().val();
+      return this.value;
+    },
+
+    /**
+       Upon search form submission, this event handler constructs a query
+       parameter object and pass it to Collection#fetch for server-side
+       filtering.
+
+       If the collection is a PageableCollection, searching will go back to the
+       first page.
+    */
+    search: function (e) {
+      if (e) e.preventDefault();
+
+      var data = {};
+      var query = this.query();
+      if (query) data[this.name] = query;
+
+      var collection = this.collection;
+
+      // go back to the first page on search
+      if (Backbone.PageableCollection &&
+          collection instanceof Backbone.PageableCollection) {
+        collection.getFirstPage({data: data, reset: true, fetch: true});
+      }
+      else collection.fetch({data: data, reset: true});
+    },
+
+    /**
+       Event handler for the clear button. Clears the search box and refetch the
+       collection.
+
+       If the collection is a PageableCollection, clearing will go back to the
+       first page.
+    */
+    clear: function (e) {
+      if (e) e.preventDefault();
+      this.clearSearchBox();
+
+      var collection = this.collection;
+
+      // go back to the first page on clear
+      if (Backbone.PageableCollection &&
+          collection instanceof Backbone.PageableCollection) {
+        collection.getFirstPage({reset: true, fetch: true});
+      }
+      else collection.fetch({reset: true});
+    },
+
+    /**
+       Renders a search form with a text box, optionally with a placeholder and
+       a preset value if supplied during initialization.
+    */
+    render: function () {
+      this.$el.empty().append(this.template({
+        name: this.name,
+        placeholder: this.placeholder,
+        value: this.value
+      }));
+      this.showClearButtonMaybe();
+      this.delegateEvents();
+      return this;
+    }
+
+  });
+
+  /**
+     ClientSideFilter is a search form widget that searches a collection for
+     model matches against a query on the client side. The exact matching
+     algorithm can be overriden by subclasses.
+
+     @class Backgrid.Extension.ClientSideFilter
+     @extends Backgrid.Extension.ServerSideFilter
+  */
+  var ClientSideFilter = Backgrid.Extension.ClientSideFilter = ServerSideFilter.extend({
+
+    /** @property */
+    events: _.extend({}, ServerSideFilter.prototype.events, {
+      "click a[data-backgrid-action=clear]": function (e) {
+        e.preventDefault();
+        this.clear();
+      },
+      "keydown input[type=search]": "search",
+      "submit": function (e) {
+        e.preventDefault();
+        this.search();
+      }
+    }),
+
+    /**
+       @property {?Array.<string>} [fields] A list of model field names to
+       search for matches. If null, all of the fields will be searched.
+    */
+    fields: null,
+
+    /**
+       @property [wait=149] The time in milliseconds to wait since the last
+       change to the search box's value before searching. This value can be
+       adjusted depending on how often the search box is used and how large the
+       search index is.
+    */
+    wait: 149,
+
+    /**
+       Debounces the #search and #clear methods and makes a copy of the given
+       collection for searching.
+
+       @param {Object} options
+       @param {Backbone.Collection} options.collection
+       @param {string} [options.placeholder]
+       @param {string} [options.fields]
+       @param {string} [options.wait=149]
+    */
+    initialize: function (options) {
+      ClientSideFilter.__super__.initialize.apply(this, arguments);
+
+      this.fields = options.fields || this.fields;
+      this.wait = options.wait || this.wait;
+
+      this._debounceMethods(["search", "clear"]);
+
+      var collection = this.collection = this.collection.fullCollection || this.collection;
+      var shadowCollection = this.shadowCollection = collection.clone();
+
+      this.listenTo(collection, "add", function (model, collection, options) {
+        shadowCollection.add(model, options);
+      });
+      this.listenTo(collection, "remove", function (model, collection, options) {
+        shadowCollection.remove(model, options);
+      });
+      this.listenTo(collection, "sort", function (col) {
+        if (!this.searchBox().val()) shadowCollection.reset(col.models);
+      });
+      this.listenTo(collection, "reset", function (col, options) {
+        options = _.extend({reindex: true}, options || {});
+        if (options.reindex && options.from == null && options.to == null) {
+          shadowCollection.reset(col.models);
+        }
+      });
+    },
+
+    _debounceMethods: function (methodNames) {
+      if (_.isString(methodNames)) methodNames = [methodNames];
+
+      this.undelegateEvents();
+
+      for (var i = 0, l = methodNames.length; i < l; i++) {
+        var methodName = methodNames[i];
+        var method = this[methodName];
+        this[methodName] = _.debounce(method, this.wait);
+      }
+
+      this.delegateEvents();
+    },
+
+    /**
+       Constructs a Javascript regular expression object for #makeMatcher.
+
+       This default implementation takes a query string and returns a Javascript
+       RegExp object that matches any of the words contained in the query string
+       case-insensitively. Override this method to return a different regular
+       expression matcher if this behavior is not desired.
+
+       @param {string} query The search query in the search box.
+       @return {RegExp} A RegExp object to match against model #fields.
+     */
+    makeRegExp: function (query) {
+      return new RegExp(query.trim().split(/\s+/).join("|"), "i");
+    },
+
+    /**
+       This default implementation takes a query string and returns a matcher
+       function that looks for matches in the model's #fields or all of its
+       fields if #fields is null, for any of the words in the query
+       case-insensitively using the regular expression object returned from
+       #makeRegExp.
+
+       Most of time, you'd want to override the regular expression used for
+       matching. If so, please refer to the #makeRegExp documentation,
+       otherwise, you can override this method to return a custom matching
+       function.
+
+       Subclasses overriding this method must take care to conform to the
+       signature of the matcher function. The matcher function is a function
+       that takes a model as paramter and returns true if the model matches a
+       search, or false otherwise.
+
+       In addition, when the matcher function is called, its context will be
+       bound to this ClientSideFilter object so it has access to the filter's
+       attributes and methods.
+
+       @param {string} query The search query in the search box.
+       @return {function(Backbone.Model):boolean} A matching function.
+    */
+    makeMatcher: function (query) {
+      var regexp = this.makeRegExp(query);
+      return function (model) {
+        var keys = this.fields || model.keys();
+        for (var i = 0, l = keys.length; i < l; i++) {
+          if (regexp.test(model.get(keys[i]) + "")) return true;
+        }
+        return false;
+      };
+    },
+
+    /**
+       Takes the query from the search box, constructs a matcher with it and
+       loops through collection looking for matches. Reset the given collection
+       when all the matches have been found.
+
+       If the collection is a PageableCollection, searching will go back to the
+       first page.
+    */
+    search: function () {
+      var matcher = _.bind(this.makeMatcher(this.query()), this);
+      var col = this.collection;
+      if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
+      col.reset(this.shadowCollection.filter(matcher), {reindex: false});
+    },
+
+    /**
+       Clears the search box and reset the collection to its original.
+
+       If the collection is a PageableCollection, clearing will go back to the
+       first page.
+    */
+    clear: function () {
+      this.clearSearchBox();
+      var col = this.collection;
+      if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
+      col.reset(this.shadowCollection.models, {reindex: false});
+    }
+
+  });
+
+  /**
+     LunrFilter is a ClientSideFilter that uses [lunrjs](http://lunrjs.com/) to
+     index the text fields of each model for a collection, and performs
+     full-text searching.
+
+     @class Backgrid.Extension.LunrFilter
+     @extends Backgrid.Extension.ClientSideFilter
+  */
+  var LunrFilter = Backgrid.Extension.LunrFilter = ClientSideFilter.extend({
+
+    /**
+       @property {string} [ref="id"]｀lunrjs` document reference attribute name.
+    */
+    ref: "id",
+
+    /**
+       @property {Object} fields A hash of `lunrjs` index field names and boost
+       value. Unlike ClientSideFilter#fields, LunrFilter#fields is _required_ to
+       initialize the index.
+    */
+    fields: null,
+
+    /**
+       Indexes the underlying collection on construction. The index will refresh
+       when the underlying collection is reset. If any model is added, removed
+       or if any indexed fields of any models has changed, the index will be
+       updated.
+
+       @param {Object} options
+       @param {Backbone.Collection} options.collection
+       @param {string} [options.placeholder]
+       @param {string} [options.ref] ｀lunrjs` document reference attribute name.
+       @param {Object} [options.fields] A hash of `lunrjs` index field names and
+       boost value.
+       @param {number} [options.wait]
+    */
+    initialize: function (options) {
+      LunrFilter.__super__.initialize.apply(this, arguments);
+
+      this.ref = options.ref || this.ref;
+
+      var collection = this.collection = this.collection.fullCollection || this.collection;
+      this.listenTo(collection, "add", this.addToIndex);
+      this.listenTo(collection, "remove", this.removeFromIndex);
+      this.listenTo(collection, "reset", this.resetIndex);
+      this.listenTo(collection, "change", this.updateIndex);
+
+      this.resetIndex(collection);
+    },
+
+    /**
+       Reindex the collection. If `options.reindex` is `false`, this method is a
+       no-op.
+
+       @param {Backbone.Collection} collection
+       @param {Object} [options]
+       @param {boolean} [options.reindex=true]
+    */
+    resetIndex: function (collection, options) {
+      options = _.extend({reindex: true}, options || {});
+
+      if (options.reindex) {
+        var self = this;
+        this.index = lunr(function () {
+          _.each(self.fields, function (boost, fieldName) {
+            this.field(fieldName, boost);
+            this.ref(self.ref);
+          }, this);
+        });
+
+        collection.each(function (model) {
+          this.addToIndex(model);
+        }, this);
+      }
+    },
+
+    /**
+       Adds the given model to the index.
+
+       @param {Backbone.Model} model
+    */
+    addToIndex: function (model) {
+      var index = this.index;
+      var doc = model.toJSON();
+      if (index.documentStore.has(doc[this.ref])) index.update(doc);
+      else index.add(doc);
+    },
+
+    /**
+       Removes the given model from the index.
+
+       @param {Backbone.Model} model
+    */
+    removeFromIndex: function (model) {
+      var index = this.index;
+      var doc = model.toJSON();
+      if (index.documentStore.has(doc[this.ref])) index.remove(doc);
+    },
+
+    /**
+       Updates the index for the given model.
+
+       @param {Backbone.Model} model
+    */
+    updateIndex: function (model) {
+      var changed = model.changedAttributes();
+      if (changed && !_.isEmpty(_.intersection(_.keys(this.fields),
+                                               _.keys(changed)))) {
+        this.index.update(model.toJSON());
+      }
+    },
+
+    /**
+       Takes the query from the search box and performs a full-text search on
+       the client-side. The search result is returned by resetting the
+       underlying collection to the models after interrogating the index for the
+       query answer.
+
+       If the collection is a PageableCollection, searching will go back to the
+       first page.
+    */
+    search: function () {
+      var col = this.collection;
+      if (!this.query()) {
+        col.reset(this.shadowCollection.models, {reindex: false});
+        return;
+      }
+
+      var searchResults = this.index.search(this.query());
+      var models = [];
+      for (var i = 0; i < searchResults.length; i++) {
+        var result = searchResults[i];
+        models.push(this.shadowCollection.get(result.ref));
+      }
+
+      if (col.pageableCollection) col.pageableCollection.getFirstPage({silent: true});
+      col.reset(models, {reindex: false});
+    }
+
+  });
+
+}));
diff --git a/web/pgadmin/static/js/backgrid/backgrid-filter.min.js b/web/pgadmin/static/js/backgrid/backgrid-filter.min.js
new file mode 100644
index 0000000..6b0423e
--- /dev/null
+++ b/web/pgadmin/static/js/backgrid/backgrid-filter.min.js
@@ -0,0 +1,8 @@
+/*
+  backgrid-filter
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+!function(a,b){"object"==typeof exports?!function(){var a;try{a=require("lunr")}catch(c){}module.exports=b(require("underscore"),require("backbone"),require("backgrid"),a)}():b(a._,a.Backbone,a.Backgrid,a.lunr)}(this,function(a,b,c,d){"use strict";var e=c.Extension.ServerSideFilter=b.View.extend({tagName:"form",className:"backgrid-filter form-search",template:function(a){return'<span class="search">&nbsp;</span><input type="search" '+(a.placeholder?'placeholder="'+a.placeholder+'"':"")+' name="'+a.name+'" '+(a.value?'value="'+a.value+'"':"")+'/><a class="clear" data-backgrid-action="clear" href="#">&times;</a>'},events:{"keyup input[type=search]":"showClearButtonMaybe","click a[data-backgrid-action=clear]":"clear",submit:"search"},name:"q",value:null,placeholder:null,initialize:function(a){e.__super__.initialize.apply(this,arguments),this.name=a.name||this.name,this.value=a.value||this.value,this.placeholder=a.placeholder||this.placeholder,this.template=a.template||this.template;var c=this.collection,d=this;b.PageableCollection&&c instanceof b.PageableCollection&&"server"==c.mode&&(c.queryParams[this.name]=function(){return d.searchBox().val()||null})},clearSearchBox:function(){this.value=null,this.searchBox().val(null),this.showClearButtonMaybe()},showClearButtonMaybe:function(){var a=this.clearButton(),b=this.searchBox().val();b?a.show():a.hide()},searchBox:function(){return this.$el.find("input[type=search]")},clearButton:function(){return this.$el.find("a[data-backgrid-action=clear]")},query:function(){return this.value=this.searchBox().val(),this.value},search:function(a){a&&a.preventDefault();var c={},d=this.query();d&&(c[this.name]=d);var e=this.collection;b.PageableCollection&&e instanceof b.PageableCollection?e.getFirstPage({data:c,reset:!0,fetch:!0}):e.fetch({data:c,reset:!0})},clear:function(a){a&&a.preventDefault(),this.clearSearchBox();var c=this.collection;b.PageableCollection&&c instanceof b.PageableCollection?c.getFirstPage({reset:!0,fetch:!0}):c.fetch({reset:!0})},render:function(){return this.$el.empty().append(this.template({name:this.name,placeholder:this.placeholder,value:this.value})),this.showClearButtonMaybe(),this.delegateEvents(),this}}),f=c.Extension.ClientSideFilter=e.extend({events:a.extend({},e.prototype.events,{"click a[data-backgrid-action=clear]":function(a){a.preventDefault(),this.clear()},"keydown input[type=search]":"search",submit:function(a){a.preventDefault(),this.search()}}),fields:null,wait:149,initialize:function(b){f.__super__.initialize.apply(this,arguments),this.fields=b.fields||this.fields,this.wait=b.wait||this.wait,this._debounceMethods(["search","clear"]);var c=this.collection=this.collection.fullCollection||this.collection,d=this.shadowCollection=c.clone();this.listenTo(c,"add",function(a,b,c){d.add(a,c)}),this.listenTo(c,"remove",function(a,b,c){d.remove(a,c)}),this.listenTo(c,"sort",function(a){this.searchBox().val()||d.reset(a.models)}),this.listenTo(c,"reset",function(b,c){c=a.extend({reindex:!0},c||{}),c.reindex&&null==c.from&&null==c.to&&d.reset(b.models)})},_debounceMethods:function(b){a.isString(b)&&(b=[b]),this.undelegateEvents();for(var c=0,d=b.length;d>c;c++){var e=b[c],f=this[e];this[e]=a.debounce(f,this.wait)}this.delegateEvents()},makeRegExp:function(a){return new RegExp(a.trim().split(/\s+/).join("|"),"i")},makeMatcher:function(a){var b=this.makeRegExp(a);return function(a){for(var c=this.fields||a.keys(),d=0,e=c.length;e>d;d++)if(b.test(a.get(c[d])+""))return!0;return!1}},search:function(){var b=a.bind(this.makeMatcher(this.query()),this),c=this.collection;c.pageableCollection&&c.pageableCollection.getFirstPage({silent:!0}),c.reset(this.shadowCollection.filter(b),{reindex:!1})},clear:function(){this.clearSearchBox();var a=this.collection;a.pageableCollection&&a.pageableCollection.getFirstPage({silent:!0}),a.reset(this.shadowCollection.models,{reindex:!1})}}),g=c.Extension.LunrFilter=f.extend({ref:"id",fields:null,initialize:function(a){g.__super__.initialize.apply(this,arguments),this.ref=a.ref||this.ref;var b=this.collection=this.collection.fullCollection||this.collection;this.listenTo(b,"add",this.addToIndex),this.listenTo(b,"remove",this.removeFromIndex),this.listenTo(b,"reset",this.resetIndex),this.listenTo(b,"change",this.updateIndex),this.resetIndex(b)},resetIndex:function(b,c){if(c=a.extend({reindex:!0},c||{}),c.reindex){var e=this;this.index=d(function(){a.each(e.fields,function(a,b){this.field(b,a),this.ref(e.ref)},this)}),b.each(function(a){this.addToIndex(a)},this)}},addToIndex:function(a){var b=this.index,c=a.toJSON();b.documentStore.has(c[this.ref])?b.update(c):b.add(c)},removeFromIndex:function(a){var b=this.index,c=a.toJSON();b.documentStore.has(c[this.ref])&&b.remove(c)},updateIndex:function(b){var c=b.changedAttributes();c&&!a.isEmpty(a.intersection(a.keys(this.fields),a.keys(c)))&&this.index.update(b.toJSON())},search:function(){var a=this.collection;if(!this.query())return void a.reset(this.shadowCollection.models,{reindex:!1});for(var b=this.index.search(this.query()),c=[],d=0;d<b.length;d++){var e=b[d];c.push(this.shadowCollection.get(e.ref))}a.pageableCollection&&a.pageableCollection.getFirstPage({silent:!0}),a.reset(c,{reindex:!1})}})});
\ No newline at end of file
diff --git a/web/pgadmin/static/js/backgrid/backgrid-paginator.js b/web/pgadmin/static/js/backgrid/backgrid-paginator.js
new file mode 100644
index 0000000..811026f
--- /dev/null
+++ b/web/pgadmin/static/js/backgrid/backgrid-paginator.js
@@ -0,0 +1,433 @@
+/*
+  backgrid-paginator
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+(function (root, factory) {
+
+  // CommonJS
+  if (typeof exports == "object") {
+    module.exports = factory(require("underscore"),
+                             require("backbone"),
+                             require("backgrid"),
+                             require("backbone.paginator"));
+  }
+  // AMD. Register as an anonymous module.
+  else if (typeof define === 'function' && define.amd) {
+    define(['underscore', 'backbone', 'backgrid', 'backbone.paginator'], factory);
+  }
+  // Browser
+  else {
+    factory(root._, root.Backbone, root.Backgrid);
+  }
+
+}(this, function (_, Backbone, Backgrid) {
+
+  "use strict";
+
+  /**
+     PageHandle is a class that renders the actual page handles and reacts to
+     click events for pagination.
+
+     This class acts in two modes - control or discrete page handle modes. If
+     one of the `is*` flags is `true`, an instance of this class is under
+     control page handle mode. Setting a `pageIndex` to an instance of this
+     class under control mode has no effect and the correct page index will
+     always be inferred from the `is*` flag. Only one of the `is*` flags should
+     be set to `true` at a time. For example, an instance of this class cannot
+     simultaneously be a rewind control and a fast forward control. A `label`
+     and a `title` function or a string are required to be passed to the
+     constuctor under this mode. If a `title` function is provided, it __MUST__
+     accept a hash parameter `data`, which contains a key `label`. Its result
+     will be used to render the generated anchor's title attribute.
+
+     If all of the `is*` flags is set to `false`, which is the default, an
+     instance of this class will be in discrete page handle mode. An instance
+     under this mode requires the `pageIndex` to be passed from the constructor
+     as an option and it __MUST__ be a 0-based index of the list of page numbers
+     to render. The constuctor will normalize the base to the same base the
+     underlying PageableCollection collection instance uses. A `label` is not
+     required under this mode, which will default to the equivalent 1-based page
+     index calculated from `pageIndex` and the underlying PageableCollection
+     instance. A provided `label` will still be honored however. The `title`
+     parameter is also not required under this mode, in which case the default
+     `title` function will be used. You are encouraged to provide your own
+     `title` function however if you wish to localize the title strings.
+
+     If this page handle represents the current page, an `active` class will be
+     placed on the root list element.
+
+     If this page handle is at the border of the list of pages, a `disabled`
+     class will be placed on the root list element.
+
+     Only page handles that are neither `active` nor `disabled` will respond to
+     click events and triggers pagination.
+
+     @class Backgrid.Extension.PageHandle
+  */
+  var PageHandle = Backgrid.Extension.PageHandle = Backbone.View.extend({
+
+    /** @property */
+    tagName: "li",
+
+    /** @property */
+    events: {
+      "click a": "changePage"
+    },
+
+    /**
+       @property {string|function(Object.<string, string>): string} title
+       The title to use for the `title` attribute of the generated page handle
+       anchor elements. It can be a string or a function that takes a `data`
+       parameter, which contains a mandatory `label` key which provides the
+       label value to be displayed.
+    */
+    title: function (data) {
+      return 'Page ' + data.label;
+    },
+
+    /**
+       @property {boolean} isRewind Whether this handle represents a rewind
+       control
+    */
+    isRewind: false,
+
+    /**
+       @property {boolean} isBack Whether this handle represents a back
+       control
+    */
+    isBack: false,
+
+    /**
+       @property {boolean} isForward Whether this handle represents a forward
+       control
+    */
+    isForward: false,
+
+    /**
+       @property {boolean} isFastForward Whether this handle represents a fast
+       forward control
+    */
+    isFastForward: false,
+
+    /**
+       Initializer.
+
+       @param {Object} options
+       @param {Backbone.Collection} options.collection
+       @param {number} pageIndex 0-based index of the page number this handle
+       handles. This parameter will be normalized to the base the underlying
+       PageableCollection uses.
+       @param {string} [options.label] If provided it is used to render the
+       anchor text, otherwise the normalized pageIndex will be used
+       instead. Required if any of the `is*` flags is set to `true`.
+       @param {string} [options.title]
+       @param {boolean} [options.isRewind=false]
+       @param {boolean} [options.isBack=false]
+       @param {boolean} [options.isForward=false]
+       @param {boolean} [options.isFastForward=false]
+    */
+    initialize: function (options) {
+      var collection = this.collection;
+      var state = collection.state;
+      var currentPage = state.currentPage;
+      var firstPage = state.firstPage;
+      var lastPage = state.lastPage;
+
+      _.extend(this, _.pick(options,
+                            ["isRewind", "isBack", "isForward", "isFastForward"]));
+
+      var pageIndex;
+      if (this.isRewind) pageIndex = firstPage;
+      else if (this.isBack) pageIndex = Math.max(firstPage, currentPage - 1);
+      else if (this.isForward) pageIndex = Math.min(lastPage, currentPage + 1);
+      else if (this.isFastForward) pageIndex = lastPage;
+      else {
+        pageIndex = +options.pageIndex;
+        pageIndex = (firstPage ? pageIndex + 1 : pageIndex);
+      }
+      this.pageIndex = pageIndex;
+
+      this.label = (options.label || (firstPage ? pageIndex : pageIndex + 1)) + '';
+      var title = options.title || this.title;
+      this.title = _.isFunction(title) ? title({label: this.label}) : title;
+    },
+
+    /**
+       Renders a clickable anchor element under a list item.
+    */
+    render: function () {
+      this.$el.empty();
+      var anchor = document.createElement("a");
+      anchor.href = '#';
+      if (this.title) anchor.title = this.title;
+      anchor.innerHTML = this.label;
+      this.el.appendChild(anchor);
+
+      var collection = this.collection;
+      var state = collection.state;
+      var currentPage = state.currentPage;
+      var pageIndex = this.pageIndex;
+
+      if (this.isRewind && currentPage == state.firstPage ||
+         this.isBack && !collection.hasPreviousPage() ||
+         this.isForward && !collection.hasNextPage() ||
+         this.isFastForward && (currentPage == state.lastPage || state.totalPages < 1)) {
+        this.$el.addClass("disabled");
+      }
+      else if (!(this.isRewind ||
+                 this.isBack ||
+                 this.isForward ||
+                 this.isFastForward) &&
+               state.currentPage == pageIndex) {
+        this.$el.addClass("active");
+      }
+
+      this.delegateEvents();
+      return this;
+    },
+
+    /**
+       jQuery click event handler. Goes to the page this PageHandle instance
+       represents. No-op if this page handle is currently active or disabled.
+    */
+    changePage: function (e) {
+      e.preventDefault();
+      var $el = this.$el, col = this.collection;
+      if (!$el.hasClass("active") && !$el.hasClass("disabled")) {
+        if (this.isRewind) col.getFirstPage();
+        else if (this.isBack) col.getPreviousPage();
+        else if (this.isForward) col.getNextPage();
+        else if (this.isFastForward) col.getLastPage();
+        else col.getPage(this.pageIndex, {reset: true});
+      }
+      return this;
+    }
+
+  });
+
+  /**
+     Paginator is a Backgrid extension that renders a series of configurable
+     pagination handles. This extension is best used for splitting a large data
+     set across multiple pages. If the number of pages is larger then a
+     threshold, which is set to 10 by default, the page handles are rendered
+     within a sliding window, plus the rewind, back, forward and fast forward
+     control handles. The individual control handles can be turned off.
+
+     @class Backgrid.Extension.Paginator
+  */
+  var Paginator = Backgrid.Extension.Paginator = Backbone.View.extend({
+
+    /** @property */
+    className: "backgrid-paginator",
+
+    /** @property */
+    windowSize: 10,
+
+    /**
+       @property {number} slideScale the number used by #slideHowMuch to scale
+       `windowSize` to yield the number of pages to slide. For example, the
+       default windowSize(10) * slideScale(0.5) yields 5, which means the window
+       will slide forward 5 pages as soon as you've reached page 6. The smaller
+       the scale factor the less pages to slide, and vice versa.
+
+       Also See:
+
+       - #slideMaybe
+       - #slideHowMuch
+    */
+    slideScale: 0.5,
+
+    /**
+       @property {Object.<string, Object.<string, string>>} controls You can
+       disable specific control handles by setting the keys in question to
+       null. The defaults will be merged with your controls object, with your
+       changes taking precedent.
+    */
+    controls: {
+      rewind: {
+        label: "《",
+        title: "First"
+      },
+      back: {
+        label: "〈",
+        title: "Previous"
+      },
+      forward: {
+        label: "〉",
+        title: "Next"
+      },
+      fastForward: {
+        label: "》",
+        title: "Last"
+      }
+    },
+
+    /** @property */
+    renderIndexedPageHandles: true,
+
+    /**
+       @property {Backgrid.Extension.PageHandle} pageHandle. The PageHandle
+       class to use for rendering individual handles
+    */
+    pageHandle: PageHandle,
+
+    /** @property */
+    goBackFirstOnSort: true,
+
+    /**
+       Initializer.
+
+       @param {Object} options
+       @param {Backbone.Collection} options.collection
+       @param {boolean} [options.controls]
+       @param {boolean} [options.pageHandle=Backgrid.Extension.PageHandle]
+       @param {boolean} [options.goBackFirstOnSort=true]
+    */
+    initialize: function (options) {
+      var self = this;
+      self.controls = _.defaults(options.controls || {}, self.controls,
+                                 Paginator.prototype.controls);
+
+      _.extend(self, _.pick(options || {}, "windowSize", "pageHandle",
+                            "slideScale", "goBackFirstOnSort",
+                            "renderIndexedPageHandles"));
+
+      var col = self.collection;
+      self.listenTo(col, "add", self.render);
+      self.listenTo(col, "remove", self.render);
+      self.listenTo(col, "reset", self.render);
+      self.listenTo(col, "backgrid:sorted", function () {
+        if (self.goBackFirstOnSort) col.getFirstPage({reset: true});
+      });
+    },
+
+    /**
+      Decides whether the window should slide. This method should return 1 if
+      sliding should occur and 0 otherwise. The default is sliding should occur
+      if half of the pages in a window has been reached.
+
+      __Note__: All the parameters have been normalized to be 0-based.
+
+      @param {number} firstPage
+      @param {number} lastPage
+      @param {number} currentPage
+      @param {number} windowSize
+      @param {number} slideScale
+
+      @return {0|1}
+     */
+    slideMaybe: function (firstPage, lastPage, currentPage, windowSize, slideScale) {
+      return Math.round(currentPage % windowSize / windowSize);
+    },
+
+    /**
+      Decides how many pages to slide when sliding should occur. The default
+      simply scales the `windowSize` to arrive at a fraction of the `windowSize`
+      to increment.
+
+      __Note__: All the parameters have been normalized to be 0-based.
+
+      @param {number} firstPage
+      @param {number} lastPage
+      @param {number} currentPage
+      @param {number} windowSize
+      @param {number} slideScale
+
+      @return {number}
+     */
+    slideThisMuch: function (firstPage, lastPage, currentPage, windowSize, slideScale) {
+      return ~~(windowSize * slideScale);
+    },
+
+    _calculateWindow: function () {
+      var collection = this.collection;
+      var state = collection.state;
+
+      // convert all indices to 0-based here
+      var firstPage = state.firstPage;
+      var lastPage = +state.lastPage;
+      lastPage = Math.max(0, firstPage ? lastPage - 1 : lastPage);
+      var currentPage = Math.max(state.currentPage, state.firstPage);
+      currentPage = firstPage ? currentPage - 1 : currentPage;
+      var windowSize = this.windowSize;
+      var slideScale = this.slideScale;
+      var windowStart = Math.floor(currentPage / windowSize) * windowSize;
+      if (currentPage <= lastPage - this.slideThisMuch()) {
+        windowStart += (this.slideMaybe(firstPage, lastPage, currentPage, windowSize, slideScale) *
+                        this.slideThisMuch(firstPage, lastPage, currentPage, windowSize, slideScale));
+      }
+      var windowEnd = Math.min(lastPage + 1, windowStart + windowSize);
+      return [windowStart, windowEnd];
+    },
+
+    /**
+       Creates a list of page handle objects for rendering.
+
+       @return {Array.<Object>} an array of page handle objects hashes
+    */
+    makeHandles: function () {
+
+      var handles = [];
+      var collection = this.collection;
+
+      var window = this._calculateWindow();
+      var winStart = window[0], winEnd = window[1];
+
+      if (this.renderIndexedPageHandles) {
+        for (var i = winStart; i < winEnd; i++) {
+          handles.push(new this.pageHandle({
+            collection: collection,
+            pageIndex: i
+          }));
+        }
+      }
+
+      var controls = this.controls;
+      _.each(["back", "rewind", "forward", "fastForward"], function (key) {
+        var value = controls[key];
+        if (value) {
+          var handleCtorOpts = {
+            collection: collection,
+            title: value.title,
+            label: value.label
+          };
+          handleCtorOpts["is" + key.slice(0, 1).toUpperCase() + key.slice(1)] = true;
+          var handle = new this.pageHandle(handleCtorOpts);
+          if (key == "rewind" || key == "back") handles.unshift(handle);
+          else handles.push(handle);
+        }
+      }, this);
+
+      return handles;
+    },
+
+    /**
+       Render the paginator handles inside an unordered list.
+    */
+    render: function () {
+      this.$el.empty();
+
+      if (this.handles) {
+        for (var i = 0, l = this.handles.length; i < l; i++) {
+          this.handles[i].remove();
+        }
+      }
+
+      var handles = this.handles = this.makeHandles();
+
+      var ul = document.createElement("ul");
+      for (var i = 0; i < handles.length; i++) {
+        ul.appendChild(handles[i].render().el);
+      }
+
+      this.el.appendChild(ul);
+
+      return this;
+    }
+
+  });
+
+}));
diff --git a/web/pgadmin/static/js/backgrid/backgrid-paginator.min.js b/web/pgadmin/static/js/backgrid/backgrid-paginator.min.js
new file mode 100644
index 0000000..0c9e6ab
--- /dev/null
+++ b/web/pgadmin/static/js/backgrid/backgrid-paginator.min.js
@@ -0,0 +1,8 @@
+/*
+  backgrid-paginator
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+!function(a,b){"object"==typeof exports&&(module.exports=b(require("underscore"),require("backbone"),require("backgrid"),require("backbone.paginator"))),"function"==typeof define&&define.amd?define(["underscore","backbone","backgrid","backbone.paginator"],b):b(a._,a.Backbone,a.Backgrid)}(this,function(a,b,c){"use strict";var d=c.Extension.PageHandle=b.View.extend({tagName:"li",events:{"click a":"changePage"},title:function(a){return"Page "+a.label},isRewind:!1,isBack:!1,isForward:!1,isFastForward:!1,initialize:function(b){var c=this.collection,d=c.state,e=d.currentPage,f=d.firstPage,g=d.lastPage;a.extend(this,a.pick(b,["isRewind","isBack","isForward","isFastForward"]));var h;this.isRewind?h=f:this.isBack?h=Math.max(f,e-1):this.isForward?h=Math.min(g,e+1):this.isFastForward?h=g:(h=+b.pageIndex,h=f?h+1:h),this.pageIndex=h,this.label=(b.label||(f?h:h+1))+"";var i=b.title||this.title;this.title=a.isFunction(i)?i({label:this.label}):i},render:function(){this.$el.empty();var a=document.createElement("a");a.href="#",this.title&&(a.title=this.title),a.innerHTML=this.label,this.el.appendChild(a);var b=this.collection,c=b.state,d=c.currentPage,e=this.pageIndex;return this.isRewind&&d==c.firstPage||this.isBack&&!b.hasPreviousPage()||this.isForward&&!b.hasNextPage()||this.isFastForward&&(d==c.lastPage||c.totalPages<1)?this.$el.addClass("disabled"):this.isRewind||this.isBack||this.isForward||this.isFastForward||c.currentPage!=e||this.$el.addClass("active"),this.delegateEvents(),this},changePage:function(a){a.preventDefault();var b=this.$el,c=this.collection;return b.hasClass("active")||b.hasClass("disabled")||(this.isRewind?c.getFirstPage():this.isBack?c.getPreviousPage():this.isForward?c.getNextPage():this.isFastForward?c.getLastPage():c.getPage(this.pageIndex,{reset:!0})),this}}),e=c.Extension.Paginator=b.View.extend({className:"backgrid-paginator",windowSize:10,slideScale:.5,controls:{rewind:{label:"《",title:"First"},back:{label:"〈",title:"Previous"},forward:{label:"〉",title:"Next"},fastForward:{label:"》",title:"Last"}},renderIndexedPageHandles:!0,pageHandle:d,goBackFirstOnSort:!0,initialize:function(b){var c=this;c.controls=a.defaults(b.controls||{},c.controls,e.prototype.controls),a.extend(c,a.pick(b||{},"windowSize","pageHandle","slideScale","goBackFirstOnSort","renderIndexedPageHandles"));var d=c.collection;c.listenTo(d,"add",c.render),c.listenTo(d,"remove",c.render),c.listenTo(d,"reset",c.render),c.listenTo(d,"backgrid:sorted",function(){c.goBackFirstOnSort&&d.getFirstPage({reset:!0})})},slideMaybe:function(a,b,c,d){return Math.round(c%d/d)},slideThisMuch:function(a,b,c,d,e){return~~(d*e)},_calculateWindow:function(){var a=this.collection,b=a.state,c=b.firstPage,d=+b.lastPage;d=Math.max(0,c?d-1:d);var e=Math.max(b.currentPage,b.firstPage);e=c?e-1:e;var f=this.windowSize,g=this.slideScale,h=Math.floor(e/f)*f;e<=d-this.slideThisMuch()&&(h+=this.slideMaybe(c,d,e,f,g)*this.slideThisMuch(c,d,e,f,g));var i=Math.min(d+1,h+f);return[h,i]},makeHandles:function(){var b=[],c=this.collection,d=this._calculateWindow(),e=d[0],f=d[1];if(this.renderIndexedPageHandles)for(var g=e;f>g;g++)b.push(new this.pageHandle({collection:c,pageIndex:g}));var h=this.controls;return a.each(["back","rewind","forward","fastForward"],function(a){var d=h[a];if(d){var e={collection:c,title:d.title,label:d.label};e["is"+a.slice(0,1).toUpperCase()+a.slice(1)]=!0;var f=new this.pageHandle(e);"rewind"==a||"back"==a?b.unshift(f):b.push(f)}},this),b},render:function(){if(this.$el.empty(),this.handles)for(var a=0,b=this.handles.length;b>a;a++)this.handles[a].remove();for(var c=this.handles=this.makeHandles(),d=document.createElement("ul"),a=0;a<c.length;a++)d.appendChild(c[a].render().el);return this.el.appendChild(d),this}})});
\ No newline at end of file
diff --git a/web/pgadmin/static/js/backgrid/backgrid-select-all.js b/web/pgadmin/static/js/backgrid/backgrid-select-all.js
new file mode 100644
index 0000000..bed1cfd
--- /dev/null
+++ b/web/pgadmin/static/js/backgrid/backgrid-select-all.js
@@ -0,0 +1,294 @@
+/*
+  backgrid-select-all
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+(function (root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(["backbone", "backgrid", "underscore"], factory);
+  } else if (typeof exports == "object") {
+    // CommonJS
+    module.exports = factory(require("backbone"), require("backgrid"), require("underscore"));
+  }
+  // Browser
+  else factory(root.Backbone, root.Backgrid, root._);
+
+}(this, function (Backbone, Backgrid, _) {
+
+  "use strict";
+
+  /**
+     Renders a checkbox for row selection.
+
+     @class Backgrid.Extension.SelectRowCell
+     @extends Backbone.View
+  */
+  var SelectRowCell = Backgrid.Extension.SelectRowCell = Backbone.View.extend({
+
+    /** @property */
+    className: "select-row-cell",
+
+    /** @property */
+    tagName: "td",
+
+    /** @property */
+    events: {
+      "keydown input[type=checkbox]": "onKeydown",
+      "change input[type=checkbox]": "onChange",
+      "click input[type=checkbox]": "enterEditMode"
+    },
+
+    /**
+       Initializer. If the underlying model triggers a `select` event, this cell
+       will change its checked value according to the event's `selected` value.
+
+       @param {Object} options
+       @param {Backgrid.Column} options.column
+       @param {Backbone.Model} options.model
+    */
+    initialize: function (options) {
+
+      this.column = options.column;
+      if (!(this.column instanceof Backgrid.Column)) {
+        this.column = new Backgrid.Column(this.column);
+      }
+
+      var column = this.column, model = this.model, $el = this.$el;
+      this.listenTo(column, "change:renderable", function (column, renderable) {
+        $el.toggleClass("renderable", renderable);
+      });
+
+      if (Backgrid.callByNeed(column.renderable(), column, model)) $el.addClass("renderable");
+
+      this.listenTo(model, "backgrid:select", function (model, selected) {
+        this.checkbox().prop("checked", selected).change();
+      });
+    },
+
+    /**
+       Returns the checkbox.
+     */
+    checkbox: function () {
+      return this.$el.find("input[type=checkbox]");
+    },
+
+    /**
+       Focuses the checkbox.
+    */
+    enterEditMode: function () {
+      this.checkbox().focus();
+    },
+
+    /**
+       Unfocuses the checkbox.
+    */
+    exitEditMode: function () {
+      this.checkbox().blur();
+    },
+
+    /**
+       Process keyboard navigation.
+    */
+    onKeydown: function (e) {
+      var command = new Backgrid.Command(e);
+      if (command.passThru()) return true; // skip ahead to `change`
+      if (command.cancel()) {
+        e.stopPropagation();
+        this.checkbox().blur();
+      }
+      else if (command.save() || command.moveLeft() || command.moveRight() ||
+               command.moveUp() || command.moveDown()) {
+        e.preventDefault();
+        e.stopPropagation();
+        this.model.trigger("backgrid:edited", this.model, this.column, command);
+      }
+    },
+
+    /**
+       When the checkbox's value changes, this method will trigger a Backbone
+       `backgrid:selected` event with a reference of the model and the
+       checkbox's `checked` value.
+    */
+    onChange: function () {
+      var checked = this.checkbox().prop("checked");
+      this.$el.parent().toggleClass("selected", checked);
+      this.model.trigger("backgrid:selected", this.model, checked);
+    },
+
+    /**
+       Renders a checkbox in a table cell.
+    */
+    render: function () {
+      this.$el.empty().append('<input tabindex="-1" type="checkbox" />');
+      this.delegateEvents();
+      return this;
+    }
+
+  });
+
+  /**
+     Renders a checkbox to select all rows on the current page.
+
+     @class Backgrid.Extension.SelectAllHeaderCell
+     @extends Backgrid.Extension.SelectRowCell
+  */
+  var SelectAllHeaderCell = Backgrid.Extension.SelectAllHeaderCell = SelectRowCell.extend({
+
+    /** @property */
+    className: "select-all-header-cell",
+
+    /** @property */
+    tagName: "th",
+
+    /**
+       Initializer. When this cell's checkbox is checked, a Backbone
+       `backgrid:select` event will be triggered for each model for the current
+       page in the underlying collection. If a `SelectRowCell` instance exists
+       for the rows representing the models, they will check themselves. If any
+       of the SelectRowCell instances trigger a Backbone `backgrid:selected`
+       event with a `false` value, this cell will uncheck its checkbox. In the
+       event of a Backbone `backgrid:refresh` event, which is triggered when the
+       body refreshes its rows, which can happen under a number of conditions
+       such as paging or the columns were reset, this cell will still remember
+       the previously selected models and trigger a Backbone `backgrid:select`
+       event on them such that the SelectRowCells can recheck themselves upon
+       refreshing.
+
+       @param {Object} options
+       @param {Backgrid.Column} options.column
+       @param {Backbone.Collection} options.collection
+    */
+    initialize: function (options) {
+
+      this.column = options.column;
+      if (!(this.column instanceof Backgrid.Column)) {
+        this.column = new Backgrid.Column(this.column);
+      }
+
+      var collection = this.collection;
+      var selectedModels = this.selectedModels = {};
+      this.listenTo(collection.fullCollection || collection,
+                    "backgrid:selected", function (model, selected) {
+        if (selected) selectedModels[model.id || model.cid] = 1;
+        else {
+          delete selectedModels[model.id || model.cid];
+          this.checkbox().prop("checked", false);
+        }
+        if (_.keys(selectedModels).length === (collection.fullCollection|| collection).length) {
+          this.checkbox().prop("checked", true);
+        }
+      });
+
+      this.listenTo(collection.fullCollection || collection, "remove", function (model) {
+        delete selectedModels[model.id || model.cid];
+        if ((collection.fullCollection || collection).length === 0) {
+          this.checkbox().prop("checked", false);
+        }
+      });
+
+      this.listenTo(collection, "backgrid:refresh", function () {
+        if ((collection.fullCollection || collection).length === 0) {
+          this.checkbox().prop("checked", false);
+        }
+        else {
+          var checked = this.checkbox().prop("checked");
+          for (var i = 0; i < collection.length; i++) {
+            var model = collection.at(i);
+            if (checked || selectedModels[model.id || model.cid]) {
+              model.trigger("backgrid:select", model, true);
+            }
+          }
+        }
+      });
+
+      var column = this.column, $el = this.$el;
+      this.listenTo(column, "change:renderable", function (column, renderable) {
+        $el.toggleClass("renderable", renderable);
+      });
+
+      if (Backgrid.callByNeed(column.renderable(), column, collection)) $el.addClass("renderable");
+    },
+
+    /**
+       Propagates the checked value of this checkbox to all the models of the
+       underlying collection by triggering a Backbone `backgrid:select` event on
+       the models on the current page, passing each model and the current
+       `checked` value of the checkbox in each event.
+
+       A `backgrid:selected` event will also be triggered with the current
+       `checked` value on all the models regardless of whether they are on the
+       current page.
+
+       This method triggers a 'backgrid:select-all' event on the collection
+       afterwards.
+    */
+    onChange: function () {
+      var checked = this.checkbox().prop("checked");
+
+      var collection = this.collection;
+      collection.each(function (model) {
+        model.trigger("backgrid:select", model, checked);
+      });
+
+      if (collection.fullCollection) {
+        collection.fullCollection.each(function (model) {
+          if (!collection.get(model.cid)) {
+            model.trigger("backgrid:selected", model, checked);
+          }
+        });
+      }
+
+      this.collection.trigger("backgrid:select-all", this.collection, checked);
+    }
+
+  });
+
+  /**
+     Convenient method to retrieve a list of selected models. This method only
+     exists when the `SelectAll` extension has been included. Selected models
+     are retained across pagination.
+
+     @member Backgrid.Grid
+     @return {Array.<Backbone.Model>}
+  */
+  Backgrid.Grid.prototype.getSelectedModels = function () {
+    var selectAllHeaderCell;
+    var headerCells = this.header.row.cells;
+    for (var i = 0, l = headerCells.length; i < l; i++) {
+      var headerCell = headerCells[i];
+      if (headerCell instanceof SelectAllHeaderCell) {
+        selectAllHeaderCell = headerCell;
+        break;
+      }
+    }
+
+    var result = [];
+    if (selectAllHeaderCell) {
+      var selectedModels = selectAllHeaderCell.selectedModels;
+      var collection = this.collection.fullCollection || this.collection;
+      for (var modelId in selectedModels) {
+        result.push(collection.get(modelId));
+      }
+    }
+
+    return result;
+  };
+
+  /**
+     Convenient method to deselect the selected models. This method is only
+     available when the `SelectAll` extension has been included.
+
+     @member Backgrid.Grid
+   */
+  Backgrid.Grid.prototype.clearSelectedModels = function () {
+    var selectedModels = this.getSelectedModels();
+    for (var i = 0, l = selectedModels.length; i < l; i++) {
+      var model = selectedModels[i];
+      model.trigger("backgrid:select", model, false);
+    }
+  };
+
+}));
diff --git a/web/pgadmin/static/js/backgrid/backgrid-select-all.min.js b/web/pgadmin/static/js/backgrid/backgrid-select-all.min.js
new file mode 100644
index 0000000..1e49adf
--- /dev/null
+++ b/web/pgadmin/static/js/backgrid/backgrid-select-all.min.js
@@ -0,0 +1,8 @@
+/*
+  backgrid-select-all
+  http://github.com/wyuenho/backgrid
+
+  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
+  Licensed under the MIT @license.
+*/
+!function(a,b){"object"==typeof exports?module.exports=b(require("backbone"),require("backgrid")):b(a.Backbone,a.Backgrid)}(this,function(a,b){"use strict";var c=b.Extension.SelectRowCell=a.View.extend({className:"select-row-cell",tagName:"td",events:{"keydown input[type=checkbox]":"onKeydown","change input[type=checkbox]":"onChange","click input[type=checkbox]":"enterEditMode"},initialize:function(a){this.column=a.column,this.column instanceof b.Column||(this.column=new b.Column(this.column));var c=this.column,d=this.model,e=this.$el;this.listenTo(c,"change:renderable",function(a,b){e.toggleClass("renderable",b)}),b.callByNeed(c.renderable(),c,d)&&e.addClass("renderable"),this.listenTo(d,"backgrid:select",function(a,b){this.$el.find("input[type=checkbox]").prop("checked",b).change()})},enterEditMode:function(){this.$el.find("input[type=checkbox]").focus()},exitEditMode:function(){this.$el.find("input[type=checkbox]").blur()},onKeydown:function(a){var c=new b.Command(a);return c.passThru()?!0:(c.cancel()?(a.stopPropagation(),this.$el.find("input[type=checkbox]").blur()):(c.save()||c.moveLeft()||c.moveRight()||c.moveUp()||c.moveDown())&&(a.preventDefault(),a.stopPropagation(),this.model.trigger("backgrid:edited",this.model,this.column,c)),void 0)},onChange:function(){var a=this.$el.find("input[type=checkbox]").prop("checked");this.$el.parent().toggleClass("selected",a),this.model.trigger("backgrid:selected",this.model,a)},render:function(){return this.$el.empty().append('<input tabindex="-1" type="checkbox" />'),this.delegateEvents(),this}}),d=b.Extension.SelectAllHeaderCell=c.extend({className:"select-all-header-cell",tagName:"th",initialize:function(a){this.column=a.column,this.column instanceof b.Column||(this.column=new b.Column(this.column));var c=this.collection,d=this.selectedModels={};this.listenTo(c.fullCollection||c,"backgrid:selected",function(a,b){b?d[a.id||a.cid]=1:(delete d[a.id||a.cid],this.$el.find("input[type=checkbox]").prop("checked",!1))}),this.listenTo(c.fullCollection||c,"remove",function(a){delete d[a.id||a.cid]}),this.listenTo(c,"backgrid:refresh",function(){for(var a=this.$el.find("input[type=checkbox]").prop("checked"),b=0;b<c.length;b++){var e=c.at(b);(a||d[e.id||e.cid])&&e.trigger("backgrid:select",e,!0)}});var e=this.column,f=this.$el;this.listenTo(e,"change:renderable",function(a,b){f.toggleClass("renderable",b)}),b.callByNeed(e.renderable(),e,c)&&f.addClass("renderable")},onChange:function(){var a=this.$el.find("input[type=checkbox]").prop("checked"),b=this.collection;b.each(function(b){b.trigger("backgrid:select",b,a)}),b.fullCollection&&b.fullCollection.each(function(c){b.get(c.cid)||c.trigger("backgrid:selected",c,a)}),this.collection.trigger("backgrid:select-all",this.collection,a)}});b.Grid.prototype.getSelectedModels=function(){for(var a,b=this.header.row.cells,c=0,e=b.length;e>c;c++){var f=b[c];if(f instanceof d){a=f;break}}var g=[];if(a){var h=a.selectedModels,i=this.collection.fullCollection||this.collection;for(var j in h)g.push(i.get(j))}return g},b.Grid.prototype.clearSelectedModels=function(){for(var a=this.getSelectedModels(),b=0,c=a.length;c>b;b++){var d=a[b];d.trigger("backgrid:select",d,!1)}}});
\ No newline at end of file
diff --git a/web/pgadmin/templates/base.html b/web/pgadmin/templates/base.html
index a7d2484..6ea5d69 100755
--- a/web/pgadmin/templates/base.html
+++ b/web/pgadmin/templates/base.html
@@ -24,6 +24,9 @@
         <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-datepicker3.css')}}"/>
         <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-switch.css')}}"/>
         <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid.css')}}"/>
+        <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid-select-all.css' if config.DEBUG else 'css/backgrid/backgrid-select-all.min.css')}}"/>
+        <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid-paginator.css' if config.DEBUG else 'css/backgrid/backgrid-paginator.min.css')}}"/>
+        <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/backgrid/backgrid-filter.css' if config.DEBUG else 'css/backgrid/backgrid-filter.min.css')}}"/>
         <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/select2/select2.css' if config.DEBUG else 'css/select2/select2.min.css')}}"/>
 
         <!-- View specified stylesheets -->
@@ -32,6 +35,7 @@
         {% endfor %}
 
         <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='css/overrides.css') }}"/>
+        {% block css_link %}{% endblock %}
 
         <!-- Base template scripts -->
         <script type="text/javascript" src="{{ url_for('static', filename='js/require.js' if config.DEBUG else 'js/require.min.js') }}"></script>
@@ -44,6 +48,9 @@
                         "deps": ['underscore', 'jquery'],
                         "exports": 'Backbone'
                     },
+                    "backbone.paginator": {
+                        "deps": ['underscore', 'jquery', 'backbone']
+                    },
                     "bootstrap": {
                         "deps": ['jquery'],
                     },
@@ -51,6 +58,15 @@
                         "deps": ['backform'],
                         "exports": 'Backgrid',
                     },
+                    "backgrid.select.all": {
+                        "deps": ['backgrid']
+                    },
+                    "backgrid.paginator": {
+                        "deps": ['backgrid', 'backbone.paginator']
+                    },
+                    "backgrid.filter": {
+                        "deps": ['backgrid']
+                    },
                     "bootstrap.switch": {
                         "deps": ['jquery', 'bootstrap'],
                         "exports": 'jQuery.fn.bootstrapSwitch'
@@ -85,10 +101,14 @@
                     alertifyjs: "{{ url_for('static', filename='js/alertifyjs/' + ('alertify' if config.DEBUG else 'alertify.min')) }}",
                     'pgadmin.alertifyjs': "{{ url_for('static', filename='js/alertifyjs/pgadmin.defaults') }}",
                     backbone: "{{ url_for('static', filename='js/' + ('backbone' if config.DEBUG else 'backbone-min')) }}",
+                    "backbone.paginator": "{{ url_for('static', filename='js/' + ('backbone.paginator' if config.DEBUG else 'backbone.paginator.min')) }}",
                     "bootstrap.datepicker": "{{ url_for('static', filename='js/' + ('bootstrap-datepicker' if config.DEBUG else 'bootstrap-datepicker.min')) }}",
                     "bootstrap.switch": "{{ url_for('static', filename='js/' + ('bootstrap-switch' if config.DEBUG else 'bootstrap-switch.min')) }}",
                     backform: "{{ url_for('static', filename='js/backform') }}",
                     backgrid: "{{ url_for('static', filename='js/backgrid/' + ('backgrid' if config.DEBUG else 'backgrid.min')) }}",
+                    "backgrid.select.all": "{{ url_for('static', filename='js/backgrid/' + ('backgrid-select-all' if config.DEBUG else 'backgrid-select-all.min')) }}",
+                    "backgrid.paginator": "{{ url_for('static', filename='js/backgrid/' + ('backgrid-paginator' if config.DEBUG else 'backgrid-paginator.min')) }}",
+                    "backgrid.filter": "{{ url_for('static', filename='js/backgrid/' + ('backgrid-filter' if config.DEBUG else 'backgrid-filter.min')) }}",
                     "backbone.undo": "{{ url_for('static', filename='js/' + ('backbone.undo' if config.DEBUG else 'backbone.undo.min')) }}",
                     "pgadmin.backgrid": "{{ url_for('static', filename='js/backgrid/backgrid.pgadmin') }}",
                     'pgadmin.backform': "{{ url_for('static', filename='js/backform.pgadmin') }}"{% for script in current_app.javascripts %},
