diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js
index 364cad2..c5cb1ac 100644
--- a/core/misc/tableheader.js
+++ b/core/misc/tableheader.js
@@ -174,8 +174,9 @@ $.extend(TableHeader.prototype, {
    * Create the duplicate header.
    */
   createSticky: function () {
-    // Clone the table header so it inherits original jQuery properties.
-    var $stickyHeader = this.$originalHeader.clone(true);
+    // Deep clone the table header so it and its children inherit their original
+    // jQuery properties.
+    var $stickyHeader = this.$originalHeader.clone(true, true);
     // Hide the table to avoid a flash of the header clone upon page load.
     this.$stickyTable = $('<table class="sticky-header"/>')
       .css({
@@ -190,6 +191,9 @@ $.extend(TableHeader.prototype, {
 
     // Initialize all computations.
     this.recalculateSticky();
+
+    // Let other scripts know a sticky table header was created.
+    this.$stickyTable.trigger('stickyTableCreated');
   },
 
   /**
diff --git a/core/misc/tableselect.js b/core/misc/tableselect.js
index 36af864..9ea4e52 100644
--- a/core/misc/tableselect.js
+++ b/core/misc/tableselect.js
@@ -1,93 +1,260 @@
-(function ($) {
+(function ($, Drupal) {
 
 "use strict";
 
-Drupal.behaviors.tableSelect = {
-  attach: function (context, settings) {
-    // Select the inner-most table in case of nested tables.
-    $(context).find('th.select-all').closest('table').once('table-select', Drupal.tableSelect);
+Drupal.behaviors.TableSelect = {
+  attach: function (context) {
+    tableSelectInitHandler(context);
+  },
+
+  detach: function (context) {
+    tableSelectRemoveHander(context);
   }
 };
 
-Drupal.tableSelect = function () {
-  // Do not add a "Select all" checkbox if there are no rows with checkboxes in the table
-  if ($(this).find('td input:checkbox').length === 0) {
-    return;
-  }
+function tableSelectInitHandler (context) {
+  var $selectAllHeaders = $(context).find('th.select-all');
+
+  $selectAllHeaders.each(function () {
+    var $header = $(this);
+    var $table = $header.closest('table');
 
-  // Keep track of the table, which checkbox is checked and alias the settings.
-  var table = this, checkboxes, lastChecked;
-  var $table = $(table);
-  var strings = { 'selectAll': Drupal.t('Select all rows in this table'), 'selectNone': Drupal.t('Deselect all rows in this table') };
-  var updateSelectAll = function (state) {
-    // Update table's select-all checkbox (and sticky header's if available).
-    $table.prev('table.sticky-header').andSelf().find('th.select-all input:checkbox').each(function() {
-      $(this).attr('title', state ? strings.selectNone : strings.selectAll);
-      this.checked = state;
-    });
-  };
-
-  // Find all <th> with class select-all, and insert the check all checkbox.
-  $table.find('th.select-all').prepend($('<input type="checkbox" class="form-checkbox" />').attr('title', strings.selectAll)).click(function (event) {
-    if ($(event.target).is('input:checkbox')) {
-      // Loop through all checkboxes and set their state to the select all checkbox' state.
-      checkboxes.each(function () {
-        this.checked = event.target.checked;
-        // Either add or remove the selected class based on the state of the check all checkbox.
-        $(this).closest('tr').toggleClass('selected', this.checked);
+    // Do nothing if there are no rows with checkboxes in the table.
+    if ($table.find('td input:checkbox').length) {
+      $table.once('table-select', function() {
+        TableSelect.tables.push(new TableSelect($table, $header));
       });
-      // Update the title and the state of the check all box.
-      updateSelectAll(event.target.checked);
     }
   });
+}
+
+function tableSelectRemoveHander (context) {
+  var tables = TableSelect.tables;
+  var $context = $(context);
+
+  // Go through all the TabeSelect instances.
+  for (var i = 0, il = tables.length; i < il; i++) {
+    var $selectAll = $context.find(tables[i].$selectAll);
+
+    // Only remove if 'select all' was found in the given context.
+    if ($selectAll.length) {
+      var $table = tables[i].$table;
+      // Unbind all events which are not directly bound to the 'select all'
+      // checkbox.
+      $table.off('.TableSelect');
+      $table.parent().off('.TableSelect');
+
+      // Remove the 'select all' checkbox. This will also remove any events
+      // which are directly bound to the 'select all' checkbox.
+      $selectAll.remove();
+
+      // Remove this entry from the cache.
+      tables.splice(i, 1);
+
+      // Trigger an event letting other scripts know TableSelect was removed.
+      $table.trigger('TableSelectRemoved');
+    }
+  }
+}
+
+/**
+ * Constructor for the TableSelect object.
+ *
+ * TableSelect will add a 'select all' checkbox to header of the current table.
+ *
+ * @param $table
+ *   jQuery object for the parent table to add a 'select all' checkbox to.
+ * @param $header
+ *   jQuery object for the header to add a 'select all' checkbox to.
+ *
+ * @constructor
+ */
+function TableSelect ($table, $header) {
+  // Cached jQuery selectors.
+  this.$table = $table;
+  this.$header = $header;
+  this.$selectAll = null;
+
+  // DOM collections.
+  this.checkboxes = [];
+  this.rows = [];
+
+  // Keep track of 'select all' state.
+  this.selectAllState = null;
+
+  // Keep track of checkbox properties.
+  this.checkboxChecked = 0;
+  this.checkboxMaxChecked = 0;
+  this.lastState = [];
+  this.lastIndex = null;
+
+  // Create 'select all' checkbox and behaviour.
+  this.createSelectAll();
+}
+
+/**
+ * Store the state of TableSelect.
+ */
+$.extend(TableSelect, {
+  /**
+   * This will store the state of all processed tables.
+   *
+   * @type {Array}
+   */
+  tables: []
+});
+
+/**
+ * Extend TableSelect prototype.
+ */
+$.extend(TableSelect.prototype, {
+  titleSelectAll: {
+    all:  Drupal.t('Select all rows in this table'),
+    none: Drupal.t('Deselect all rows in this table')
+  },
+
+  createSelectAll: function () {
+    // Store the checkboxes, initial state and their parent rows.
+    var $checkboxes = this.$table.find('td input:checkbox');
+    for (var i = 0, il = $checkboxes.length; i < il; i++) {
+      // Reference to heckbox DOM node.
+      this.checkboxes[i] = $checkboxes[i];
+
+      // Only count enabled checkboxes.
+      if (!$checkboxes[i].disabled) {
+        this.checkboxMaxChecked++;
+        if ($checkboxes[i].checked) {
+          this.checkboxChecked++;
+        }
+      }
+
+      // Keep track of the 'checked' state of checkboxes.
+      this.lastState[i] = $checkboxes[i].checked;
+
+      // Reference to parent table row DOM node.
+      this.rows[i] = $($checkboxes[i]).closest('tr')[0];
+    }
 
-  // For each of the checkboxes within the table that are not disabled.
-  checkboxes = $table.find('td input:checkbox:enabled').click(function (e) {
-    // Either add or remove the selected class based on the state of the check all checkbox.
-    $(this).closest('tr').toggleClass('selected', this.checked);
+    // Create the 'select all' checkbox.
+    this.$selectAll = $('<input type="checkbox" class="form-checkbox" />');
+    this.$selectAll.on('click.TableSelect', {TableSelect: this}, this.toggleSelectAll);
+
+    // Update the title and selected state of the 'select all' checkbox.
+    this.updateSelectAll();
+
+    // Add the 'select all' checkbox to the table header.
+    this.$selectAll.prependTo(this.$header);
+
+    // Listen for the creation of sticky tables. Do this on the direct parent of
+    // the table, to minimise the actual execution of the following function.
+    this.$table.parent().on('stickyTableCreated.TableSelect', {TableSelect: this}, this.adaptToSticky);
+
+    // Keep track of the checkboxes state.
+    this.$table.on('click.TableSelect', 'td input:checkbox', {TableSelect: this}, this.toggleCheckbox);
+
+    // Let other scripts know a 'select all' checkbox was created.
+    this.$table.trigger('TableSelectCreated');
+  },
+
+  adaptToSticky: function (e) {
+    var self = e.data.TableSelect;
+    var $stickyTable = self.$table.prev('table.sticky-header');
+
+    // If there is a sticky table header attached to the current table, add the
+    // sticky table 'select all' checkbox to the cached 'select all' selector.
+    if ($stickyTable.length) {
+      var $stickyCheckbox = $stickyTable.find('th.select-all input:checkbox');
+      self.$selectAll = $([self.$selectAll[0], $stickyCheckbox[0]]);
+    }
+  },
+
+  toggleSelectAll: function (e) {
+    var self = e.data.TableSelect;
+    var state = $(this).prop('checked');
+
+    // Update the title and selected state of the 'select all' checkbox.
+    self.updateSelectAll(state);
+
+    // Toggle all checkboxes.
+    self.toggleCheckboxes(0, self.checkboxes.length - 1, state);
+  },
+
+  updateSelectAll: function (state) {
+    // If the state parameter was omitted, find a default state value based on
+    // the number of checkboxes selected.
+    if (typeof state === 'undefined') {
+      // If all checkboxes are checked, make sure the select-all one is checked
+      // too, otherwise keep it unchecked.
+      state = (this.checkboxChecked === this.checkboxMaxChecked) ? true : false;
+    }
+
+    // Only do the actual updating if the 'select all' checkbox does not have
+    // the desired state already.
+    if (this.selectAllState !== state) {
+      // Set the title and 'checked' state explicitly in the event there are
+      // multiple checkboxes controlling the same table. Otherwise these
+      // checkboxes would not be in sync.
+      var title = state ? this.titleSelectAll.none : this.titleSelectAll.all;
+      this.$selectAll.attr('title', title).prop('checked', state);
+      this.selectAllState = state;
+    }
+  },
+
+  toggleCheckbox: function (e) {
+    var self = e.data.TableSelect;
+    var currentIndex = $(self.checkboxes).index(this);
+    var lastIndex = self.lastIndex;
 
     // If this is a shift click, we need to highlight everything in the range.
     // Also make sure that we are actually checking checkboxes over a range and
     // that a checkbox has been checked or unchecked before.
-    if (e.shiftKey && lastChecked && lastChecked !== e.target) {
-      // We use the checkbox's parent TR to do our range searching.
-      Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked);
+    var from, to;
+    if (e.shiftKey && lastIndex !== null && lastIndex !== currentIndex) {
+      from = (lastIndex < currentIndex) ? lastIndex : currentIndex;
+      to =   (lastIndex < currentIndex) ? currentIndex : lastIndex;
+    }
+    else {
+      from = currentIndex;
+      to =   currentIndex;
     }
+    self.toggleCheckboxes(from, to, this.checked);
 
-    // If all checkboxes are checked, make sure the select-all one is checked too, otherwise keep unchecked.
-    updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
+    // Update the most recent toggled checkbox.
+    self.lastIndex = currentIndex;
 
-    // Keep track of the last checked checkbox.
-    lastChecked = e.target;
-  });
-};
+    // Update the title and selected state of the 'select all' checkbox.
+    self.updateSelectAll();
+  },
 
-Drupal.tableSelectRange = function (from, to, state) {
-  // We determine the looping mode based on the the order of from and to.
-  var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
+  toggleCheckboxes: function (from, to, state) {
+    var checkboxes = this.checkboxes;
+    var rows = this.rows;
+    var lastState = this.lastState;
+    var checkboxChecked = this.checkboxChecked;
 
-  // Traverse through the sibling nodes.
-  for (var i = from[mode], $i; i; i = i[mode]) {
-    // Make sure that we're only dealing with elements.
-    if (i.nodeType !== 1) {
-      continue;
-    }
-    $i = $(i);
-    // Either add or remove the selected class based on the state of the target checkbox.
-    $i.toggleClass('selected', state);
-    $i.find('input:checkbox').attr('checked', state);
-
-    if (to.nodeType) {
-      // If we are at the end of the range, stop.
-      if (i === to) {
-        break;
+    // Gather all the non-disabled checkboxes that need to be toggled.
+    var applyCheckboxes = [];
+    var applyRows = [];
+    for (var i = from, il = to; i <= il; i++) {
+      if (!checkboxes[i].disabled) {
+        applyCheckboxes.push(checkboxes[i]);
+        applyRows.push(rows[i]);
+        if (lastState[i] !== state) {
+          checkboxChecked += state ? 1 : -1;
+        }
+        lastState[i] = state;
       }
     }
-    // A faster alternative to doing $(i).filter(to).length.
-    else if ($.filter(to, [i]).r.length) {
-      break;
-    }
+    this.lastState = lastState;
+    this.checkboxChecked = checkboxChecked;
+
+    // Toggle both the 'checked' property and the 'selected' class.
+    $(applyCheckboxes).prop('checked', state);
+    $(applyRows).toggleClass('selected', state);
   }
-};
+});
+
+// Expose constructor in the public space.
+Drupal.TableSelect = TableSelect;
 
-})(jQuery);
+}(jQuery, Drupal));
