Преглед изворни кода

HUE-4910 [editor] Improved performance of fixed result headers

This adds a new version of the jQuery table extender plugin dedicated to the editor that contains a bunch of improvements and bug fixes found in the old table extender plugin. The old plugin has been reverted to it's initial state to prevent issues elsewhere in Hue.
Johan Ahlen пре 9 година
родитељ
комит
eebb265

+ 23 - 12
desktop/core/src/desktop/static/desktop/js/jquery.huedatatable.js

@@ -36,8 +36,14 @@
       } else {
         self.$table.find('tr').find('td:eq(' + index + '),th:eq(' + index + ')').show();
       }
-      self.$table.data('plugin_jHueTableExtender').drawLockedRows(true);
-    }
+      if (self.$table.data('plugin_jHueTableExtender')) {
+        self.$table.data('plugin_jHueTableExtender').drawLockedRows(true);
+      } else if (self.$table.data('plugin_jHueTableExtender2')) {
+        self.$table.data('plugin_jHueTableExtender2').drawHeader();
+        self.$table.data('plugin_jHueTableExtender2').drawLockedRows(true);
+        self.$table.data('plugin_jHueTableExtender2').repositionHeader();
+      }
+    };
 
     self.fnToggleAllCols = function (visible) {
       var aoColumns = self.$table.data('aoColumns');
@@ -52,11 +58,16 @@
       } else {
         self.$table.find('th, td').show();
       }
-      self.$table.data('plugin_jHueTableExtender').drawLockedRows(true);
-    }
+      if (self.$table.data('plugin_jHueTableExtender')) {
+        self.$table.data('plugin_jHueTableExtender').drawLockedRows(true);
+      } else if (self.$table.data('plugin_jHueTableExtender2')) {
+        self.$table.data('plugin_jHueTableExtender2').drawHeader();
+        self.$table.data('plugin_jHueTableExtender2').drawLockedRows(true);
+        self.$table.data('plugin_jHueTableExtender2').repositionHeader();
+      }
+    };
 
     self.fnSortColumn = function (obj, way) {
-      var $t = self.$table;
       var data = self.$table.data('data');
 
       var idx = obj.originalIndex;
@@ -278,6 +289,9 @@
             if ($t.data('plugin_jHueTableExtender')) {
               $t.data('plugin_jHueTableExtender').drawFirstColumn();
             }
+            if ($t.data('plugin_jHueTableExtender2')) {
+              $t.data('plugin_jHueTableExtender2').drawFirstColumn();
+            }
           }
           else {
             if (force) {
@@ -289,6 +303,9 @@
               if ($t.data('plugin_jHueTableExtender')) {
                 $t.data('plugin_jHueTableExtender').drawFirstColumn();
               }
+              if ($t.data('plugin_jHueTableExtender2')) {
+                $t.data('plugin_jHueTableExtender2').drawFirstColumn();
+              }
             }
           }
 
@@ -338,12 +355,6 @@
             $t.data('plugin_jHueTableExtender').drawHeader(typeof force === 'undefined');
             $t.data('plugin_jHueTableExtender').drawLockedRows();
           }
-
-          if (force) {
-            $t.data('plugin_jHueTableExtender').drawFirstColumn();
-            $t.data('plugin_jHueTableExtender').drawLockedRows();
-          }
-
         }
         $t.data('fnDraws', $t.data('fnDraws') + 1);
         if ($t.data('oInit')['fnDrawCallback']) {
@@ -354,7 +365,7 @@
 
         self.isDrawing = false;
       }
-    }
+    };
 
     self.fnAddData = function (mData, bRedraw) {
       var $t = self.$table;

+ 104 - 146
desktop/core/src/desktop/static/desktop/js/jquery.tableextender.js

@@ -42,11 +42,10 @@
     };
 
   function Plugin(element, options) {
-    var self = this;
-    self.element = element;
-    self.setOptions(options);
-    self._name = pluginName;
-    self.init();
+    this.element = element;
+    this.setOptions(options);
+    this._name = pluginName;
+    this.init();
   }
 
   Plugin.prototype.setOptions = function (options) {
@@ -57,7 +56,8 @@
       if (options != null) {
         this.options.labels = $.extend({}, extendedDefaults.labels, options.labels);
       }
-    } else {
+    }
+    else {
       this.options = $.extend({}, defaults, options);
       if (options != null) {
         this.options.labels = $.extend({}, defaults.labels, options.labels);
@@ -88,11 +88,11 @@
 
   Plugin.prototype.drawHeader = function (skipCreation) {
     drawHeader(this, skipCreation);
-  };
+  }
 
   Plugin.prototype.drawFirstColumn = function () {
     drawFirstColumn(this);
-  };
+  }
 
   Plugin.prototype.drawLockedRows = function (force) {
     var _this = this;
@@ -103,28 +103,27 @@
         drawLockedRow(_this, idx.substr(1), force);
       });
     }
-  };
+  }
+
 
   Plugin.prototype.init = function () {
-    var self = this;
 
     $.expr[":"].econtains = function (obj, index, meta, stack) {
       return (obj.textContent || obj.innerText || $(obj).text() || "").toLowerCase() == meta[3].toLowerCase();
-    };
-
-    var $element = $(self.element);
+    }
 
-    if (self.options.includeNavigator) {
+    var _this = this;
+    if (_this.options.includeNavigator) {
       var jHueTableExtenderNavigator = $("<div>").attr("id", "jHueTableExtenderNavigator");
       $("<a>").attr("href", "#").addClass("pull-right").html("&times;").click(function (e) {
         e.preventDefault();
         $(this).parent().hide();
       }).appendTo(jHueTableExtenderNavigator);
-      $("<label>").html(self.options.labels.GO_TO_COLUMN + " <input type=\"text\" placeholder=\"" + self.options.labels.PLACEHOLDER + "\" />").appendTo(jHueTableExtenderNavigator);
+      $("<label>").html(_this.options.labels.GO_TO_COLUMN + " <input type=\"text\" placeholder=\"" + _this.options.labels.PLACEHOLDER + "\" />").appendTo(jHueTableExtenderNavigator);
 
       jHueTableExtenderNavigator.appendTo($("body"));
 
-      $element.find("tbody").click(function (event) {
+      $(_this.element).find("tbody").click(function (event) {
         if ($.trim(getSelection()) == "") {
           window.setTimeout(function () {
             $(".rowSelected").removeClass("rowSelected");
@@ -141,7 +140,7 @@
       });
 
       var source = [];
-      $element.find("th").each(function () {
+      $(_this.element).find("th").each(function () {
         source.push($(this).text());
       });
 
@@ -149,22 +148,22 @@
         source: source,
         updater: function (item) {
           jHueTableExtenderNavigator.hide();
-          $element.find("tr td:nth-child(" + ($(self.element).find("th:econtains(" + item + ")").index() + 1) + ")").addClass("columnSelected");
-          if (self.options.firstColumnTooltip) {
-            $element.find("tr td:nth-child(" + ($(self.element).find("th:econtains(" + item + ")").index() + 1) + ")").each(function () {
+          $(_this.element).find("tr td:nth-child(" + ($(_this.element).find("th:econtains(" + item + ")").index() + 1) + ")").addClass("columnSelected");
+          if (_this.options.firstColumnTooltip) {
+            $(_this.element).find("tr td:nth-child(" + ($(_this.element).find("th:econtains(" + item + ")").index() + 1) + ")").each(function () {
               $(this).attr("rel", "tooltip").attr("title", "#" + $(this).parent().find("td:first-child").text()).tooltip({
                 placement: 'left'
               });
             });
           }
-          $(self.element).parent().animate({
-            scrollLeft: $(self.element).find("th:econtains(" + item + ")").position().left + $(self.element).parent().scrollLeft() - $(self.element).parent().offset().left - 30
+          $(_this.element).parent().animate({
+            scrollLeft: $(_this.element).find("th:econtains(" + item + ")").position().left + $(_this.element).parent().scrollLeft() - $(_this.element).parent().offset().left - 30
           }, 300);
-          $(self.element).find("tr.rowSelected td:nth-child(" + ($(self.element).find("th:econtains(" + item + ")").index() + 1) + ")").addClass("cellSelected");
+          $(_this.element).find("tr.rowSelected td:nth-child(" + ($(_this.element).find("th:econtains(" + item + ")").index() + 1) + ")").addClass("cellSelected");
         }
       });
 
-      $element.parent().bind("mouseleave", function () {
+      $(_this.element).parent().bind("mouseleave", function () {
         jHueTableExtenderNavigator.hide();
       });
 
@@ -173,28 +172,27 @@
       });
     }
 
-    if (self.options.hintElement != null) {
-      var $hintElement = $(self.options.hintElement);
+    if (_this.options.hintElement != null) {
       var showAlertTimeout = -1;
-      $element.find("tbody").mousemove(function () {
+      $(_this.element).find("tbody").mousemove(function () {
         window.clearTimeout(showAlertTimeout);
-        if ($hintElement.data("show") == null || $hintElement.data("show")) {
+        if ($(_this.options.hintElement).data("show") == null || $(_this.options.hintElement).data("show")) {
           showAlertTimeout = window.setTimeout(function () {
-            $hintElement.fadeIn();
+            $(_this.options.hintElement).fadeIn();
           }, 1300);
         }
       });
 
-      $hintElement.find(".close").click(function () {
-        $hintElement.data("show", false);
+      $(_this.options.hintElement).find(".close").click(function () {
+        $(_this.options.hintElement).data("show", false);
       });
     }
 
-    if (self.options.fixedHeader) {
-      drawHeader(self);
+    if (_this.options.fixedHeader) {
+      drawHeader(_this);
     }
-    if (self.options.fixedFirstColumn) {
-      drawFirstColumn(self);
+    if (_this.options.fixedFirstColumn) {
+      drawFirstColumn(_this);
     }
 
     $(document).on('click dblclick', '.dataTables_wrapper > table tbody tr', function () {
@@ -207,79 +205,11 @@
         $('.dataTables_wrapper > .jHueTableExtenderClonedContainerColumn table tbody tr:eq('+($(this).index())+')').addClass('selected');
       }
     });
-
     $(document).on('dblclick', '.dataTables_wrapper > table tbody tr', function () {
       if (huePubSub){
         huePubSub.publish('table.row.dblclick', {idx: $(this).index(), table: $(this).parents('table')});
       }
     });
-
-    var $pluginElement = $(self.element);
-    $pluginElement.parent().resize(function () {
-      $pluginElement.data('clonedTableContainer').height($pluginElement.parent().get(0).scrollHeight);
-      $pluginElement.data('clonedTableVisibleContainer').height($pluginElement.parent().height());
-    });
-
-    var scrollThrottle = -1;
-    var lastMarginTop = -$pluginElement.parent().scrollTop() + self.options.fixedFirstColumnTopMargin;
-    var newMarginTop;
-    $pluginElement.parent().scroll(function () {
-      window.clearTimeout(scrollThrottle);
-      scrollThrottle = window.setTimeout(function () {
-        newMarginTop = -$pluginElement.parent().scrollTop() + self.options.fixedFirstColumnTopMargin;
-        if (newMarginTop !== lastMarginTop) {
-          $pluginElement.data('clonedTableContainer').css("marginTop", newMarginTop + "px");
-          lastMarginTop = newMarginTop;
-        }
-      }, 50);
-    });
-    $pluginElement.data('clonedTableContainer').css("marginTop", lastMarginTop + "px");
-
-    var topPos, pos;
-    var $parent = $pluginElement.parent();
-    function positionClones() {
-      pos = self.options.stickToTopPosition;
-      if (typeof pos === 'function'){
-        pos = pos();
-      }
-      if (pos > -1) {
-        topPos = $pluginElement.offset().top;
-        if (topPos < pos) {
-          $pluginElement.data('clonedCellVisibleContainer').css("top", pos + "px");
-        } else {
-          $pluginElement.data('clonedCellVisibleContainer').css("top", topPos + "px");
-        }
-        $pluginElement.data('clonedTableVisibleContainer').css("top", topPos + "px");
-      } else {
-        if (self.options.clonedContainerPosition == 'absolute') {
-          topPos = $parent.position().top;
-        } else {
-          topPos = $parent.offset().top;
-        }
-        $pluginElement.data('clonedTableVisibleContainer').css("top", topPos + "px");
-        $pluginElement.data('clonedCellVisibleContainer').css("top", topPos + "px");
-      }
-    }
-
-    positionClones();
-
-    var $mainScrollable = $(self.options.mainScrollable);
-    if ($mainScrollable.data('lastScroll')) {
-      $mainScrollable.off('scroll', $mainScrollable.data('lastScroll'));
-      $mainScrollable.data('lastScroll', false);
-    }
-    if (navigator.userAgent.toLowerCase().indexOf("firefox") > 0) {
-      var ffThrottle = -1;
-      var throttledPositionClones = function () {
-        window.clearTimeout(ffThrottle);
-        ffThrottle = window.setTimeout(positionClones, 10);
-      };
-      $mainScrollable.on('scroll', throttledPositionClones);
-      $mainScrollable.data('lastScroll', throttledPositionClones);
-    } else {
-      $mainScrollable.on('scroll', positionClones);
-      $mainScrollable.data('lastScroll', positionClones);
-    }
   };
 
   function drawLockedRow(plugin, rowNo, force) {
@@ -294,7 +224,7 @@
       $header.find('tr td:first-child').filter(function () {
         return $(this).text() === rowNo + '';
       }).closest('tr').remove();
-      delete lockedRows['r' + $el.text()];
+      delete lockedRows['r' + $el.text()]
       $el.parent().remove();
       if ($header.find('tbody tr').length == 0) {
         $header.removeClass('locked');
@@ -325,7 +255,8 @@
         row: $clone,
         cell: $newTr
       };
-    } else {
+    }
+    else {
       lockedRows['r' + rowNo].row.appendTo($header.find('tbody'));
       lockedRows['r' + rowNo].cell.appendTo($headerCounter.find('tbody'));
       lockedRows['r' + rowNo].cell.find('td').on('click', function () {
@@ -409,10 +340,6 @@
 
     clonedCellVisibleContainer.appendTo($pluginElement.parent());
 
-    $pluginElement.data('clonedTableContainer', clonedTableContainer);
-    $pluginElement.data('clonedCellVisibleContainer', clonedCellVisibleContainer);
-    $pluginElement.data('clonedTableVisibleContainer', clonedTableVisibleContainer);
-
     window.clearInterval($pluginElement.data('firstcol_interval'));
     var firstColInt = window.setInterval(function () {
       if ($pluginElement.parent().height() != $pluginElement.parent().data("h")) {
@@ -422,18 +349,53 @@
       }
     }, 250);
     $pluginElement.data('firstcol_interval', firstColInt);
-  }
 
-  function drawHeader(plugin, skipCreation) {
-    var $pluginElement = $(plugin.element);
+    $pluginElement.parent().resize(function () {
+      clonedTableContainer.height($pluginElement.parent().get(0).scrollHeight);
+      clonedTableVisibleContainer.height($pluginElement.parent().height());
+    });
 
-    if (! $pluginElement.is(':visible')) {
-      if (typeof $pluginElement.data('updateThWidthsInterval') !== 'undefined') {
-        window.clearInterval($pluginElement.data('updateThWidthsInterval'));
+    $pluginElement.parent().scroll(function () {
+      clonedTableContainer.css("marginTop", (-$pluginElement.parent().scrollTop() + plugin.options.fixedFirstColumnTopMargin) + "px");
+    });
+
+    clonedTableContainer.css("marginTop", (-$pluginElement.parent().scrollTop() + plugin.options.fixedFirstColumnTopMargin) + "px");
+
+    function positionClones() {
+      var pos = plugin.options.stickToTopPosition;
+      if (typeof pos === 'function'){
+        pos = pos();
+      }
+      if (pos > -1) {
+        if ($pluginElement.offset().top < pos) {
+          clonedCellVisibleContainer.css("top", pos + "px");
+        }
+        else {
+          clonedCellVisibleContainer.css("top", $pluginElement.offset().top + "px");
+        }
+        clonedTableVisibleContainer.css("top", $pluginElement.offset().top + "px");
+      }
+      else {
+        if (plugin.options.clonedContainerPosition == 'absolute') {
+          clonedTableVisibleContainer.css("top", ($pluginElement.parent().position().top) + "px");
+          clonedCellVisibleContainer.css("top", ($pluginElement.parent().position().top) + "px");
+        }
+        else {
+          clonedTableVisibleContainer.css("top", ($pluginElement.parent().offset().top) + "px");
+          clonedCellVisibleContainer.css("top", ($pluginElement.parent().offset().top) + "px");
+        }
       }
-      return;
     }
 
+    positionClones();
+
+    $(mainScrollable).on('scroll', positionClones);
+
+  }
+
+
+  function drawHeader(plugin, skipCreation) {
+    var $pluginElement = $(plugin.element);
     if (!$pluginElement.attr("id") && plugin.options.parentId) {
       $pluginElement.attr("id", "eT" + plugin.options.parentId);
     }
@@ -449,32 +411,21 @@
       clonedTableTHead.appendTo(clonedTable);
       var clonedTableTR = $pluginElement.find('thead>tr').clone();
       clonedTableTR.appendTo(clonedTableTHead);
-      $('<tbody>').appendTo(clonedTable);
+      $('<tbody>').appendTo(clonedTable)
 
-      var clonedThs = clonedTable.find("thead>tr th");
-      clonedThs.wrapInner('<span></span>');
+      clonedTable.find("thead>tr th").wrapInner('<span></span>');
 
-      if (typeof $pluginElement.data('updateThWidthsInterval') !== 'undefined') {
-        window.clearInterval($pluginElement.data('updateThWidthsInterval'));
-      }
-
-      var thMapping = [];
       $pluginElement.find("thead>tr th").each(function (i) {
         var originalTh = $(this);
         originalTh.removeAttr("data-bind");
-        var clonedTh = $(clonedThs[i]).css("background-color", "#FFFFFF").click(function () {
+        clonedTable.find("thead>tr th:eq(" + i + ")").width(originalTh.width()).css("background-color", "#FFFFFF").click(function () {
           originalTh.click();
           if (plugin.options.headerSorting) {
-            clonedThs.attr("class", "sorting");
+            clonedTable.find("thead>tr th").attr("class", "sorting");
           }
           $(this).attr("class", originalTh.attr("class"));
         });
-        thMapping.push({
-          original: originalTh,
-          clone: clonedTh
-        })
       });
-      $pluginElement.data('thMapping', thMapping);
 
       var clonedTableContainer = $("<div>").width($pluginElement.outerWidth());
       clonedTable.appendTo(clonedTableContainer);
@@ -482,7 +433,8 @@
       var topPosition;
       if (plugin.options.clonedContainerPosition == 'absolute') {
         topPosition = $pluginElement.parent().position().top - $(mainScrollable).scrollTop();
-      } else {
+      }
+      else {
         topPosition = $pluginElement.parent().offset().top - $(mainScrollable).scrollTop();
       }
       var clonedTableVisibleContainer = $("<div>").attr("id", $pluginElement.attr("id") + "jHueTableExtenderClonedContainer").addClass("jHueTableExtenderClonedContainer").width($pluginElement.parent().width()).css("overflow-x", "hidden").css("top", topPosition + "px");
@@ -491,17 +443,6 @@
       clonedTableContainer.appendTo(clonedTableVisibleContainer);
       clonedTableVisibleContainer.prependTo($pluginElement.parent());
 
-      var updateThWidths = function () {
-        clonedTableVisibleContainer.width($pluginElement.parent().width());
-        $pluginElement.data('thMapping').forEach(function (mapping) {
-          if (mapping.clone.width() !== mapping.original.width()) {
-            mapping.clone.width(mapping.original.width())
-          }
-        });
-      };
-
-      updateThWidths();
-      $pluginElement.data('updateThWidthsInterval', window.setInterval(updateThWidths, 300));
 
       function throttledHeaderPadding() {
         var firstCellWidth = clonedTable.find("thead>tr th:eq(0)").outerWidth();
@@ -511,7 +452,8 @@
             if ($(this).find('span').width() + -leftPosition < $(this).outerWidth() - 20) { // 20 is the sorting css width
               $(this).find('span').css('paddingLeft', -leftPosition);
             }
-          } else {
+          }
+          else {
             $(this).find('span').css('paddingLeft', 0);
           }
         });
@@ -531,6 +473,20 @@
 
       clonedTableVisibleContainer.scrollLeft($pluginElement.parent().scrollLeft());
 
+      $pluginElement.parent().data("w", clonedTableVisibleContainer.width());
+
+      window.clearInterval($pluginElement.data('header_interval'));
+      var headerInt = window.setInterval(function () {
+        if ($pluginElement.parent().width() != $pluginElement.parent().data("w")) {
+          clonedTableVisibleContainer.width($pluginElement.parent().width());
+          $pluginElement.parent().data("w", clonedTableVisibleContainer.width());
+          $pluginElement.find("thead>tr th").each(function (i) {
+            clonedTable.find("thead>tr th:eq(" + i + ")").width($(this).width()).css("background-color", "#FFFFFF");
+          });
+        }
+      }, 250);
+      $pluginElement.data('header_interval', headerInt);
+
       $pluginElement.parent().resize(function () {
         clonedTableVisibleContainer.width($(this).width());
       });
@@ -558,7 +514,8 @@
       positionClones();
 
       $(mainScrollable).on('scroll', positionClones);
-    } else {
+    }
+    else {
       $("#" + $pluginElement.attr("id") + "jHueTableExtenderClonedContainer").children('div').width($pluginElement.outerWidth());
       $pluginElement.find("thead>tr th").each(function (i) {
         var originalTh = $(this);
@@ -583,7 +540,8 @@
     return this.each(function () {
       if (!$.data(this, 'plugin_' + pluginName)) {
         $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
-      } else {
+      }
+      else {
         $.data(this, 'plugin_' + pluginName).resetSource();
         $.data(this, 'plugin_' + pluginName).setOptions(options);
       }

+ 421 - 0
desktop/core/src/desktop/static/desktop/js/jquery.tableextender2.js

@@ -0,0 +1,421 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+(function ($, window, document, undefined) {
+
+  var PLUGIN_NAME = "jHueTableExtender2";
+
+  var DEFAULT_OPTIONS = {
+    fixedHeader: true,
+    fixedFirstColumn: true,
+    fixedFirstColumnTopMargin: 0,
+    headerSorting: true,
+    lockSelectedRow: true,
+    firstColumnTooltip: false,
+    classToRemove: 'resultTable',
+    hintElement: null,
+    mainScrollable: window,
+    stickToTopPosition: -1,
+    labels: {
+      GO_TO_COLUMN: "Go to column:",
+      PLACEHOLDER: "column name...",
+      LOCK: "Click to lock row",
+      UNLOCK: "Click to unlock row"
+    }
+  };
+
+  function Plugin(element, options) {
+    var self = this;
+    self.disposeFunctions = [];
+
+    self.lockedRows = {};
+    self.setOptions(options); // Sets self.options
+
+    self.$element = $(element);
+    self.$parent = self.$element.parent();
+    self.$mainScrollable = $(self.options.mainScrollable);
+
+    self.drawHeader(); // Sets self.headerRowContainer
+    self.drawFirstColumn(); // Sets self.firstColumnInner, self.firstColumnTopCell and self.firstColumn
+    self.drawLockedRows();
+
+    var lastHeight = -1;
+    var currentHeight;
+
+    var adjustSizes = function () {
+      currentHeight = self.$parent.height();
+      if (currentHeight != lastHeight) {
+        self.firstColumnInner.height(self.$parent.get(0).scrollHeight);
+        self.firstColumn.height(currentHeight);
+        lastHeight = currentHeight;
+      }
+
+      self.headerRowContainer.width(self.$parent.width());
+      self.thMapping.forEach(function (mapping) {
+        if (mapping.clone.width() !== mapping.original.width()) {
+          mapping.clone.width(mapping.original.width())
+        }
+      });
+    };
+    adjustSizes();
+    var sizeInterval = window.setInterval(adjustSizes, 300);
+    self.disposeFunctions.push(function () {
+      window.clearInterval(sizeInterval);
+    });
+
+    var clickHandler = function () {
+      if ($(this).hasClass('selected')) {
+        self.$parent.find('.selected').removeClass('selected');
+      } else {
+        self.$parent.find('.selected').removeClass('selected');
+        $(this).addClass('selected');
+        self.$parent.find('.jHueTableExtenderClonedContainerColumn table tbody tr:eq(' + $(this).index() + ')').addClass('selected');
+      }
+    };
+    self.$parent.on('click dblclick', 'table tbody tr', clickHandler);
+    self.disposeFunctions.push(function () {
+      self.$parent.off('click dblclick', 'table tbody tr', clickHandler);
+    });
+
+    var dblClickHandler = function () {
+      huePubSub.publish('table.row.dblclick', {idx: $(this).index(), table: $(this).parents('table')});
+    };
+    self.$parent.on('dblclick', 'table tbody tr', dblClickHandler);
+    self.disposeFunctions.push(function () {
+      self.$parent.off('dblclick', 'table tbody tr', dblClickHandler);
+    });
+
+    self.repositionHeader();
+
+    var scrollFunction;
+    if (navigator.userAgent.toLowerCase().indexOf("firefox") > 0) {
+      var ffThrottle = -1;
+      var throttledPositionClones = function () {
+        window.clearTimeout(ffThrottle);
+        ffThrottle = window.setTimeout(self.repositionHeader.bind(self), 10);
+      };
+      scrollFunction = throttledPositionClones;
+    } else {
+      scrollFunction = self.repositionHeader.bind(self);
+    }
+    self.$mainScrollable.on('scroll', scrollFunction);
+    self.disposeFunctions.push(function () {
+      self.$mainScrollable.off('scroll', scrollFunction);
+    });
+
+    var firstCellWidth, leftPosition, $th;
+    var throttledHeaderPadding = function () {
+      firstCellWidth = self.options.fixedFirstColumn ? self.headerRowContainer.find("thead>tr th:eq(0)").outerWidth() : 0;
+      self.headerRowContainer.find("thead>tr th").each(function () {
+        $th = $(this);
+        leftPosition = $th.position().left - firstCellWidth;
+        if (leftPosition + $th.outerWidth() > 0 && leftPosition < 0) {
+          if ($th.find('span').width() - leftPosition < $th.outerWidth() - 20) { // 20 is the sorting css width
+            $th.find('span').css('paddingLeft', -leftPosition);
+          }
+        } else {
+          $th.find('span').css('paddingLeft', 0);
+        }
+      });
+    };
+
+    var scrollTimeout = -1;
+    var headerScroll = function () {
+      self.headerRowContainer.scrollLeft(self.$parent.scrollLeft());
+      window.clearTimeout(scrollTimeout);
+      scrollTimeout = window.setTimeout(throttledHeaderPadding, 200);
+    };
+    self.$parent.on('scroll', headerScroll);
+    self.disposeFunctions.push(function () {
+      self.$parent.off('scroll', headerScroll);
+    });
+
+    self.$element.bind('headerpadding', function () {
+      window.clearTimeout(scrollTimeout);
+      scrollTimeout = window.setTimeout(throttledHeaderPadding, 200);
+    });
+    self.disposeFunctions.push(function () {
+      self.$element.unbind('headerpadding');
+    });
+  }
+
+  Plugin.prototype.destroy = function () {
+    var self = this;
+    self.disposeFunctions.forEach(function (disposeFunction) {
+      disposeFunction();
+    })
+  };
+
+  Plugin.prototype.setOptions = function (options) {
+    if (typeof jHueTableExtenderGlobals != 'undefined') {
+      var extendedDefaults = $.extend({}, DEFAULT_OPTIONS, jHueTableExtenderGlobals);
+      extendedDefaults.labels = $.extend({}, DEFAULT_OPTIONS.labels, jHueTableExtenderGlobals.labels);
+      this.options = $.extend({}, extendedDefaults, options);
+      if (options != null) {
+        this.options.labels = $.extend({}, extendedDefaults.labels, options.labels);
+      }
+    } else {
+      this.options = $.extend({}, DEFAULT_OPTIONS, options);
+      if (options != null) {
+        this.options.labels = $.extend({}, DEFAULT_OPTIONS.labels, options.labels);
+      }
+    }
+  };
+
+  Plugin.prototype.repositionHeader = function () {
+    var self = this;
+    var pos = self.options.stickToTopPosition;
+    var topPos = 0;
+    if (typeof pos === 'function'){
+      pos = pos();
+    }
+    if (pos > -1) {
+      if (self.$element.offset().top < pos) {
+        topPos = pos;
+      } else {
+        topPos = self.$element.offset().top;
+      }
+      self.firstColumn.css("top", self.$element.offset().top + "px");
+    } else if (self.options.clonedContainerPosition == 'absolute') {
+      topPos = self.$parent.position().top;
+      self.firstColumn.css("top", topPos + "px");
+    } else {
+      topPos = self.$parent.offset().top;
+      self.firstColumn.css("top", topPos + "px");
+    }
+    self.headerRowContainer.css("top", topPos + "px");
+    self.firstColumnTopCell.css("top", topPos + "px");
+  };
+
+  Plugin.prototype.drawHeader = function () {
+    var self = this;
+
+    if (!self.$element.attr("id") && self.options.parentId) {
+      self.$element.attr("id", "eT" + self.options.parentId);
+    }
+
+    $("#" + self.$element.attr("id") + "jHueTableExtenderClonedContainer").remove();
+    var clonedTable = $('<table>').attr('class', self.$element.attr('class'));
+    clonedTable.removeClass(self.options.classToRemove);
+    clonedTable.css("margin-bottom", "0").css("table-layout", "fixed");
+    var clonedTableTHead = $('<thead>');
+    clonedTableTHead.appendTo(clonedTable);
+    var clonedTableTR = self.$element.find('thead>tr').clone();
+    clonedTableTR.appendTo(clonedTableTHead);
+    $('<tbody>').appendTo(clonedTable);
+
+    var clonedThs = clonedTable.find("thead>tr th");
+    clonedThs.wrapInner('<span></span>');
+
+    if (typeof self.$element.data('updateThWidthsInterval') !== 'undefined') {
+      window.clearInterval(self.$element.data('updateThWidthsInterval'));
+    }
+
+    self.thMapping = [];
+    var totalThWidth = 0;
+    self.$element.find("thead>tr th").each(function (i) {
+      var originalTh = $(this);
+      originalTh.removeAttr("data-bind");
+      var clonedTh = $(clonedThs[i]).css("background-color", "#FFFFFF").click(function () {
+        originalTh.click();
+        if (self.options.headerSorting) {
+          clonedThs.attr("class", "sorting");
+        }
+        $(this).attr("class", originalTh.attr("class"));
+      });
+      clonedTh.width(originalTh.width());
+      totalThWidth += originalTh.width();
+      self.thMapping.push({
+        original: originalTh,
+        clone: clonedTh
+      })
+    });
+
+    var headerRowDiv = $("<div>");
+    clonedTable.appendTo(headerRowDiv);
+
+    var topPosition;
+    if (self.options.clonedContainerPosition == 'absolute') {
+      topPosition = self.$parent.position().top - self.$mainScrollable.scrollTop();
+    } else {
+      topPosition = self.$parent.offset().top - self.$mainScrollable.scrollTop();
+    }
+    var headerRowContainer = $("<div>").attr("id", self.$element.attr("id") + "jHueTableExtenderClonedContainer")
+        .addClass("jHueTableExtenderClonedContainer").width(totalThWidth).css("overflow-x", "hidden").css("top", topPosition + "px");
+    headerRowContainer.css("position", self.options.clonedContainerPosition || "fixed");
+
+    headerRowDiv.appendTo(headerRowContainer);
+    headerRowContainer.prependTo(self.$parent);
+
+    headerRowContainer.scrollLeft(self.$parent.scrollLeft());
+
+    self.headerRowContainer = headerRowContainer;
+  };
+
+  Plugin.prototype.drawFirstColumn = function () {
+    var self = this;
+    if (! self.options.fixedFirstColumn) {
+      self.firstColumnInner = $();
+      self.firstColumnTopCell = $();
+      self.firstColumn = $();
+      return;
+    }
+    if (!self.$element.attr("id") && self.options.parentId) {
+      self.$element.attr("id", "eT" + self.options.parentId);
+    }
+
+    var originalTh = self.$element.find("thead>tr th:eq(0)");
+    var topPosition;
+    if (self.options.clonedContainerPosition == 'absolute') {
+      topPosition = self.$parent.position().top - self.$mainScrollable.scrollTop();
+    } else {
+      topPosition = self.$parent.offset().top - self.$mainScrollable.scrollTop();
+    }
+
+    $("#" + self.$element.attr("id") + "jHueTableExtenderClonedContainerCell").remove();
+    var clonedCell = $('<table>').attr('class', self.$element.attr('class'));
+    clonedCell.removeClass(self.options.classToRemove);
+    clonedCell.css("margin-bottom", "0").css("table-layout", "fixed");
+    var clonedCellTHead = $('<thead>');
+    clonedCellTHead.appendTo(clonedCell);
+    var clonedCellTH = originalTh.clone();
+    clonedCellTH.appendTo(clonedCellTHead);
+    clonedCellTH.width(originalTh.width()).css({
+      "background-color": "#FFFFFF",
+      "border-color": "transparent"
+    });
+    clonedCellTH.click(function () {
+      originalTh.click();
+    });
+    $('<tbody>').appendTo(clonedCell);
+
+    var clonedCellContainer = $("<div>").css("background-color", "#FFFFFF").width(originalTh.outerWidth());
+
+    clonedCell.appendTo(clonedCellContainer);
+
+    var firstColumnTopCell = $("<div>").attr("id", self.$element.attr("id") + "jHueTableExtenderClonedContainerCell").addClass("jHueTableExtenderClonedContainerCell").width(originalTh.outerWidth()).css("overflow", "hidden").css("top", topPosition + "px");
+    firstColumnTopCell.css("position", self.options.clonedContainerPosition || "fixed");
+
+    clonedCellContainer.appendTo(firstColumnTopCell);
+
+    $("#" + self.$element.attr("id") + "jHueTableExtenderClonedContainerColumn").remove();
+    var clonedTable = $('<table>').attr('class', self.$element.attr('class')).html('<thead></thead><tbody></tbody>');
+    clonedTable.removeClass(self.options.classToRemove);
+    clonedTable.css("margin-bottom", "0").css("table-layout", "fixed");
+    self.$element.find("thead>tr th:eq(0)").clone().appendTo(clonedTable.find('thead'));
+    var clonedTBody = clonedTable.find('tbody');
+    var clones = self.$element.find("tbody>tr td:nth-child(1)").clone();
+    var h = '';
+    clones.each(function(){
+      h+= '<tr><td>' + $(this).html() +'</td></tr>';
+    });
+    clonedTBody.html(h);
+    if (self.options.lockSelectedRow) {
+      clonedTBody.find('td').each(function(){
+        var cell = $(this);
+        cell.attr('title', self.options.labels.LOCK).addClass('lockable pointer').on('click', function(){
+          self.drawLockedRow($(this).text()*1);
+        });
+        $('<i>').addClass('fa fa-lock muted').prependTo(cell);
+      });
+    }
+    clonedTable.find("thead>tr th:eq(0)").width(originalTh.width()).css("background-color", "#FFFFFF");
+
+    var firstColumnInner = $("<div>").css("background-color", "#FFFFFF").width(originalTh.outerWidth()).height(self.$parent.get(0).scrollHeight);
+    clonedTable.appendTo(firstColumnInner);
+
+    var firstColumn = $("<div>").attr("id", self.$element.attr("id") + "jHueTableExtenderClonedContainerColumn").addClass("jHueTableExtenderClonedContainerColumn").width(originalTh.outerWidth()).height(self.$parent.height()).css("overflow", "hidden").css("top", topPosition + "px");
+    firstColumn.css("position", self.options.clonedContainerPosition || "fixed");
+
+    firstColumnInner.appendTo(firstColumn);
+    firstColumn.appendTo(self.$parent);
+
+    firstColumnTopCell.appendTo(self.$parent);
+
+    self.firstColumnInner = firstColumnInner;
+    self.firstColumnTopCell = firstColumnTopCell;
+    self.firstColumn = firstColumn;
+  };
+
+  Plugin.prototype.drawLockedRows = function (force) {
+    var self = this;
+    if (Object.keys(self.lockedRows).length === 0) {
+      self.headerRowContainer.find('tbody').empty();
+      self.headerRowContainer.removeClass('locked');
+      self.firstColumnTopCell.removeClass('locked');
+    } else {
+      self.headerRowContainer.addClass('locked');
+      self.firstColumnTopCell.addClass('locked');
+      Object.keys(self.lockedRows).forEach(function (idx) {
+        self.drawLockedRow(idx.substr(1), force);
+      });
+    }
+  };
+
+  Plugin.prototype.drawLockedRow = function (rowNo, force) {
+    var self = this;
+
+    function unlock($el) {
+      self.headerRowContainer.find('tr td:first-child').filter(function () {
+        return $(this).text() === rowNo + '';
+      }).closest('tr').remove();
+      delete self.lockedRows['r' + $el.text()];
+      $el.parent().remove();
+      if (self.headerRowContainer.find('tbody tr').length == 0) {
+        self.headerRowContainer.removeClass('locked');
+        self.firstColumnTopCell.removeClass('locked');
+      }
+    }
+
+    if (Object.keys(self.lockedRows).indexOf('r' + rowNo) === -1 || force) {
+      if (force) {
+        unlock(self.lockedRows['r' + rowNo].cell.find('td'));
+      }
+      var $clone = $('<tr>').addClass('locked');
+      var tHtml = '';
+      var aoColumns = self.$element.data('aoColumns');
+      self.$element.data('data')[rowNo - 1].forEach(function(col, idx){
+        tHtml += '<td ' + (aoColumns && !aoColumns[idx].bVisible ? 'style="display: none"' : '') + '>' + col + '</td>';
+      });
+      $clone.html(tHtml);
+      $clone.appendTo(self.headerRowContainer.find('tbody'));
+      var $newTr = $('<tr>');
+      $newTr.addClass('locked').html('<td class="pointer unlockable" title="' + self.options.labels.UNLOCK + '"><i class="fa fa-unlock muted"></i>' + rowNo + '</td>').appendTo(self.firstColumnTopCell.find('tbody'));
+      $newTr.find('td').on('click', function () {
+        unlock($(this));
+      });
+      self.lockedRows['r' + rowNo] = {
+        row: $clone,
+        cell: $newTr
+      };
+    } else {
+      self.lockedRows['r' + rowNo].row.appendTo(self.headerRowContainer.find('tbody'));
+      self.lockedRows['r' + rowNo].cell.appendTo(self.firstColumnTopCell.find('tbody'));
+      self.lockedRows['r' + rowNo].cell.find('td').on('click', function () {
+        unlock($(this));
+      });
+    }
+  };
+
+  $.fn[PLUGIN_NAME] = function (options) {
+    return this.each(function () {
+      if ($.data(this, 'plugin_' + PLUGIN_NAME)) {
+        $.data(this, 'plugin_' + PLUGIN_NAME).destroy();
+      }
+      $.data(this, 'plugin_' + PLUGIN_NAME, new Plugin(this, options));
+    });
+  }
+})(jQuery, window, document);

+ 1 - 0
desktop/core/src/desktop/templates/common_header.mako

@@ -192,6 +192,7 @@ if USE_NEW_EDITOR.get():
   <script src="${ static('desktop/js/jquery.horizontalscrollbar.js') }"></script>
   <script src="${ static('desktop/js/jquery.tablescroller.js') }"></script>
   <script src="${ static('desktop/js/jquery.tableextender.js') }"></script>
+  <script src="${ static('desktop/js/jquery.tableextender2.js') }"></script>
   <script src="${ static('desktop/js/jquery.scrollup.js') }"></script>
   <script src="${ static('desktop/js/jquery.tour.js') }"></script>
   <script src="${ static('desktop/ext/js/jquery/plugins/jquery.cookie.js') }"></script>

+ 51 - 75
desktop/libs/notebook/src/notebook/templates/editor_components.mako

@@ -2379,31 +2379,25 @@ ${ hueIcons.symbols() }
       contained: !vm.editorMode()
     });
 
-    if (vm.editorMode()) {
-      $(el).parents('.dataTables_wrapper').css('overflow-x', 'hidden');
-      $(el).jHueTableExtender({
-        fixedHeader: true,
-        fixedFirstColumn: true,
-        lockSelectedRow: true,
-        includeNavigator: false,
-        mainScrollable: '.right-panel',
-        stickToTopPosition: function() { return vm.isPlayerMode() ? 1 : 73 },
-        parentId: 'snippet_' + snippet.id(),
-        clonedContainerPosition: "fixed"
-      });
-      $(el).jHueHorizontalScrollbar();
-    }
-    else {
-      $(el).jHueTableExtender({
-        fixedHeader: true,
-        fixedFirstColumn: true,
-        lockSelectedRow: true,
-        includeNavigator: false,
-        mainScrollable: '.right-panel',
-        parentId: 'snippet_' + snippet.id(),
-        clonedContainerPosition: "absolute"
-      });
-    }
+    window.setTimeout(function () {
+      if (vm.editorMode()) {
+        $(el).parents('.dataTables_wrapper').css('overflow-x', 'hidden');
+        $(el).jHueTableExtender2({
+          mainScrollable: '.right-panel',
+          stickToTopPosition: function() { return vm.isPlayerMode() ? 1 : 73 },
+          parentId: 'snippet_' + snippet.id(),
+          clonedContainerPosition: "fixed"
+        });
+        $(el).jHueHorizontalScrollbar();
+      } else {
+        $(el).jHueTableExtender2({
+          mainScrollable: '.right-panel',
+          parentId: 'snippet_' + snippet.id(),
+          clonedContainerPosition: "absolute"
+        });
+      }
+    }, 0);
+
 
     return _dt;
   }
@@ -2495,9 +2489,14 @@ ${ hueIcons.symbols() }
           }
         }, 100);
       }
-    }
+    };
     scrollElement.data('scrollFnDtCreation', dataScroll);
     scrollElement.on('scroll', dataScroll);
+    snippet.isResultSettingsVisible.subscribe(function (newValue) {
+      if (newValue) {
+        dataScroll();
+      }
+    });
 
     return _dt;
   }
@@ -2507,39 +2506,31 @@ ${ hueIcons.symbols() }
     var dt = $t.hueDataTable();
     dt.fnToggleAllCols(linkElement.checked);
     dt.fnDraw();
-    if ($t.data('plugin_jHueTableExtender')) {
-      $t.data('plugin_jHueTableExtender').drawHeader();
-      $t.data('plugin_jHueTableExtender').drawLockedRows();
-    }
   }
 
   function toggleColumn(linkElement, index, snippet) {
     var $t = $(linkElement).parents(".snippet").find("table.resultTable:eq(0)");
     var dt = $t.hueDataTable();
     dt.fnSetColumnVis(index, linkElement.checked);
-    if ($t.data('plugin_jHueTableExtender')) {
-      $t.data('plugin_jHueTableExtender').drawHeader();
-      $t.data('plugin_jHueTableExtender').drawLockedRows();
-    }
   }
 
   function scrollToColumn(linkElement, index) {
-    var _t = $(linkElement).parents(".snippet").find("table.resultTable:eq(0)");
+    var $resultTable = $(linkElement).parents(".snippet").find("table.resultTable:eq(0)");
     var _text = $.trim($(linkElement).text().split(" ")[0]);
-    var _col = _t.find("th").filter(function () {
+    var _col = $resultTable.find("th").filter(function () {
       return $.trim($(this).text()) == _text;
     });
-    _t.find(".columnSelected").removeClass("columnSelected");
-    var _colSel = _t.find("tr th:nth-child(" + (_col.index() + 1) + ")");
+    $resultTable.find(".columnSelected").removeClass("columnSelected");
+    var _colSel = $resultTable.find("tr th:nth-child(" + (_col.index() + 1) + ")");
     if (_colSel.length > 0) {
-      _t.find("tr td:nth-child(" + (_col.index() + 1) + ")").addClass("columnSelected");
-      _t.parent().animate({
-        scrollLeft: _colSel.position().left + _t.parent().scrollLeft() - _t.parent().offset().left - 30
+      $resultTable.find("tr td:nth-child(" + (_col.index() + 1) + ")").addClass("columnSelected");
+      $resultTable.parent().animate({
+        scrollLeft: _colSel.position().left + $resultTable.parent().scrollLeft() - $resultTable.parent().offset().left - 30
       }, 300, function(){
-        _t.data('scrollToCol', _col.index());
-        _t.data('scrollToRow', null);
-        _t.data('scrollAnimate', true);
-        _t.parent().trigger('scroll');
+        $resultTable.data('scrollToCol', _col.index());
+        $resultTable.data('scrollToRow', null);
+        $resultTable.data('scrollAnimate', true);
+        $resultTable.parent().trigger('scroll');
       });
     }
   }
@@ -2946,35 +2937,25 @@ ${ hueIcons.symbols() }
         }
         viewModel.selectedNotebook().snippets().forEach(function (snippet) {
           if (snippet.result.meta().length > 0) {
-            var _el = $("#snippet_" + snippet.id()).find(".resultTable");
-            if (!viewModel.editorMode()) {
-              _el.jHueTableExtender({
-                fixedHeader: true,
-                fixedFirstColumn: true,
-                lockSelectedRow: true,
-                includeNavigator: false,
-                mainScrollable: '.right-panel',
-                parentId: 'snippet_' + snippet.id(),
-                clonedContainerPosition: "absolute"
-              });
-            }
-            else {
-              $('.right-panel').data('lastScroll', $('.right-panel').scrollTop());
-              $('.right-panel').trigger('scroll');
-              if (_el.data('plugin_jHueTableExtender')) {
-                _el.data('plugin_jHueTableExtender').drawHeader();
-                _el.data('plugin_jHueTableExtender').drawLockedRows();
-              }
+            var tableExtender = $("#snippet_" + snippet.id() + ' .resultTable').data('plugin_jHueTableExtender2');
+            if (typeof tableExtender !== 'undefined') {
+              tableExtender.repositionHeader();
+              tableExtender.drawLockedRows();
             }
           }
         });
-      }
+        $(".jHueTableExtenderClonedContainer").show();
+        $(".jHueTableExtenderClonedContainerColumn").show();
+        $(".jHueTableExtenderClonedContainerCell").show();
+      };
+
       if (timeout){
         window.clearTimeout(redrawTimeout);
         redrawTimeout = window.setTimeout(renderer, timeout);
       } else {
         renderer();
       }
+
       $('.right-panel').jHueScrollUp();
     };
 
@@ -3661,25 +3642,20 @@ ${ hueIcons.symbols() }
           viewModel.selectedNotebook().snippets().forEach(function (snippet) {
             if (snippet.result.data().length > 0) {
               var _elCheckerInterval = -1;
+              var _el = $("#snippet_" + snippet.id());
               _elCheckerInterval = window.setInterval(function () {
-                var _el = $("#snippet_" + snippet.id()).find(".resultTable");
-                if ($("#snippet_" + snippet.id()).find(".resultTable").length > 0) {
+                if (_el.find(".resultTable").length > 0) {
                   try {
-                    var _dt = createDatatable(_el, snippet, viewModel);
-                    _dt.fnClearTable();
-                    _dt.fnAddData(snippet.result.data());
                     resizeToggleResultSettings(snippet, true);
                     resetResultsResizer(snippet);
                     $(document).trigger("forceChartDraw", snippet);
-                    window.clearInterval(_elCheckerInterval);
-                  }
-                  catch (e) {
-                  }
+                  } catch (e) { }
+                  window.clearInterval(_elCheckerInterval);
                 }
               }, 200)
             }
           });
-        };
+        }
       }
 
       forceChartDraws();