Browse Source

[spark] Move assist panel to a separate KO component

With this commit the assist panel code is moved to a separate KO component. A new ko_components.mako is created to act as a library for this and future knockout components.

The assist instance is currently shared with codemirror for autocomplete so it currently has to be created from outside the component and passed as a reference. When we move to a new editor we should consider refactoring this as well.
Johan Ahlen 10 years ago
parent
commit
55f287391b

+ 9 - 0
apps/spark/src/spark/static/spark/css/spark.css

@@ -218,6 +218,15 @@
   margin-left: 0;
   margin-left: 0;
 }
 }
 
 
+.assist-action {
+  display: none;
+  margin: 9px 3px 3px 3px;
+}
+
+.assist-container:hover .assist-action {
+  display: inline-block;
+}
+
 .show-assist {
 .show-assist {
   position: fixed;
   position: fixed;
   top: 80px;
   top: 80px;

+ 1 - 5
apps/spark/src/spark/static/spark/js/spark.ko.js

@@ -695,11 +695,7 @@ function EditorViewModel(notebooks, options) {
 
 
   self.isAssistAvailable = ko.observable(true);
   self.isAssistAvailable = ko.observable(true);
 
 
-  self.isAssistVisible = ko.observable(options.assistVisible);
-  self.toggleAssist = function () {
-    self.isAssistVisible(!self.isAssistVisible());
-    $(document).trigger("toggleAssist");
-  };
+  self.isAssistVisible = ko.observable(false);
 
 
   self.assistContent = ko.observable();
   self.assistContent = ko.observable();
   self.assistSelectedMainObject = ko.observable();
   self.assistSelectedMainObject = ko.observable();

+ 10 - 165
apps/spark/src/spark/templates/editor.mako

@@ -19,6 +19,7 @@
   from django.utils.translation import ugettext as _
   from django.utils.translation import ugettext as _
 %>
 %>
 
 
+<%namespace name="koComponents" file="/ko_components.mako" />
 
 
 ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
 ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
 
 
@@ -214,7 +215,7 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
 </div>
 </div>
 
 
 
 
-<a title="${_('Toggle Assist')}" class="pointer show-assist" data-bind="visible: !$root.isAssistVisible() && $root.isAssistAvailable(), click: $root.toggleAssist">
+<a title="${_('Toggle Assist')}" class="pointer show-assist" data-bind="visible: !$root.isAssistVisible() && $root.isAssistAvailable(), click: function() { $root.isAssistVisible(! $root.isAssistVisible()) }">
   <i class="fa fa-chevron-right"></i>
   <i class="fa fa-chevron-right"></i>
 </a>
 </a>
 
 
@@ -230,65 +231,9 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
   </div>
   </div>
 </div>
 </div>
 
 
-<div id="assistQuickLook" class="modal hide fade">
-    <div class="modal-header">
-      <a href="#" class="close" data-dismiss="modal">&times;</a>
-      <h3>${_('Data sample for')} <span class="tableName"></span></h3>
-    </div>
-    <div class="modal-body" style="min-height: 100px">
-      <div class="loader">
-        <!--[if !IE]><!--><i class="fa fa-spinner fa-spin" style="font-size: 30px; color: #DDD"></i><!--<![endif]-->
-        <!--[if IE]><img src="${ static('desktop/art/spinner.gif') }"/><![endif]-->
-      </div>
-      <div class="sample"></div>
-    </div>
-    <div class="modal-footer">
-      <button class="btn btn-primary disable-feedback" data-dismiss="modal">${_('Ok')}</button>
-    </div>
-  </div>
-
-
 <script type="text/html" id="notebook">
 <script type="text/html" id="notebook">
   <div class="row-fluid">
   <div class="row-fluid">
-    <div class="span2 assist-container" data-bind="visible: $root.isAssistVisible, css:{'span2': $root.isAssistVisible, 'hidden': !$root.isAssistVisible()}, event: { mouseover: function(){ $('.assist-hover').show(); }, mouseout: function(){ $('.assist-hover').hide(); } }">
-      <div class="span2 assist">
-        <a title="${_('Toggle Assist')}" class="pull-right pointer assist-hover" style="margin:3px; margin-top:9px; display:none" data-bind="click: $root.toggleAssist">
-          <i class="fa fa-chevron-left"></i>
-        </a>
-        <a title="${_('Manually refresh the table list')}" rel="tooltip" data-placement="top" class="pull-right pointer assist-hover" style="margin:3px; margin-top:9px; display:none" data-bind="click: reloadAssist">
-          <i class="fa fa-refresh"></i>
-        </a>
-        <ul class="nav nav-list" style="border: none; padding: 0; background-color: #FFF; margin-bottom: 1px;">
-          <li class="nav-header">${_('database')}</li>
-        </ul>
-        <!-- ko if: $root.assistContent && $root.assistContent().mainObjects().length > 0 -->
-          <select data-bind="options: $root.assistContent().mainObjects, select2: { width: '100%', placeholder: '${ _("Choose a database...") }', update: $root.assistSelectedMainObject}" class="input-medium" data-placeholder="${_('Choose a database...')}"></select>
-          <input type="text" placeholder="${ _('Table name...') }" style="width:90%; margin-top: 20px" data-bind="value: $root.assistContent().filter, valueUpdate: 'afterkeydown'" />
-          <div data-bind="visible: Object.keys($root.assistContent().firstLevelObjects()).length == 0">${_('The selected database has no tables.')}</div>
-          <ul data-bind="visible: Object.keys($root.assistContent().firstLevelObjects()).length > 0, foreach: $root.assistContent().filteredFirstLevelObjects()" class="unstyled assist-main">
-            <li data-bind="event: { mouseover: function(){ $('#assistHover_' + $data).show(); }, mouseout: function(){ $('#assistHover_' + $data).hide(); } }">
-              <a href="javascript:void(0)" data-bind="attr: {'id': 'assistHover_' + $data}, click: showTablePreview" style="display: none; position: absolute; right: 10px; margin-left: auto; background-color: #FFF" class="preview-sample"><i class="fa fa-list" title="${'Preview Sample data'}" style="margin-left:5px"></i></a>
-              <a href="javascript:void(0)" data-bind="click: loadAssistSecondLevel, event: { 'dblclick': function(){ addToActiveCodemirror($data); } }"><span data-bind="text: $data"></span></a>
-
-              <div data-bind="visible: $root.assistContent().firstLevelObjects()[$data].loaded() && $root.assistContent().firstLevelObjects()[$data].open()">
-                <ul data-bind="visible: $root.assistContent().firstLevelObjects()[$data].items().length > 0, foreach: $root.assistContent().firstLevelObjects()[$data].items()" class="unstyled">
-                  <li><a data-bind="attr: {'title': secondLevelTitle($data)}" style="padding-left:10px" href="javascript:void(0)"><span data-bind="html: truncateSecondLevel($data), event: { 'dblclick': function(){ addToActiveCodemirror($data.name +', '); } }"></span></a></li>
-                </ul>
-              </div>
-            </li>
-          </ul>
-        <!-- /ko -->
-
-        <div id="navigatorLoader" class="center" data-bind="visible: $root.assistContent().isLoading">
-          <!--[if !IE]><!--><i class="fa fa-spinner fa-spin" style="font-size: 20px; color: #BBB"></i><!--<![endif]-->
-          <!--[if IE]><img src="${ static('desktop/art/spinner.gif') }"/><![endif]-->
-        </div>
-
-        <div class="center" data-bind="visible: $root.assistContent().hasErrors">
-          ${ _('The database list cannot be loaded.') }
-        </div>
-      </div>
-    </div>
+    <assist-panel params="assist: assist, isAssistVisible: $root.isAssistVisible, isAssistAvailable: $root.isAssistAvailable, totalStorageId: 'sparkAssistVisible'"></assist-panel>
     <div data-bind="css:{'span10': $root.isAssistVisible, 'span12 nomargin': ! $root.isAssistVisible()}">
     <div data-bind="css:{'span10': $root.isAssistVisible, 'span12 nomargin': ! $root.isAssistVisible()}">
       <div data-bind="css: {'row-fluid row-container sortable-snippets':true, 'is-editing': $root.isEditing},
       <div data-bind="css: {'row-fluid row-container sortable-snippets':true, 'is-editing': $root.isEditing},
         sortable: { template: 'snippet', data: snippets, isEnabled: $root.isEditing,
         sortable: { template: 'snippet', data: snippets, isEnabled: $root.isEditing,
@@ -751,6 +696,8 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
 
 
 
 
 
 
+${ koComponents.assistPanel() }
+
 <script type="text/javascript" charset="utf-8">
 <script type="text/javascript" charset="utf-8">
 
 
   var assist = new Assist({
   var assist = new Assist({
@@ -806,13 +753,13 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
 
 
   var activeCodemirrorEditor = null;
   var activeCodemirrorEditor = null;
 
 
-  function addToActiveCodemirror(text) {
+  huePubSub.subscribe('assist.dblClickItem', function addToActiveCodemirror(text) {
     if (activeCodemirrorEditor != null){
     if (activeCodemirrorEditor != null){
       activeCodemirrorEditor.replaceSelection(text);
       activeCodemirrorEditor.replaceSelection(text);
       activeCodemirrorEditor.setSelection(activeCodemirrorEditor.getCursor());
       activeCodemirrorEditor.setSelection(activeCodemirrorEditor.getCursor());
       activeCodemirrorEditor.focus();
       activeCodemirrorEditor.focus();
     }
     }
-  }
+  });
 
 
   ko.bindingHandlers.verticalSlide = {
   ko.bindingHandlers.verticalSlide = {
     init: function(element, valueAccessor) {
     init: function(element, valueAccessor) {
@@ -979,7 +926,7 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
                 if (_aliases[_foundTable]) {
                 if (_aliases[_foundTable]) {
                   _foundTable = _aliases[_foundTable];
                   _foundTable = _aliases[_foundTable];
                 }
                 }
-                assist.getData(viewModel.assistContent().selectedMainObject() + "/" + _foundTable);
+                assist.getData(assist.selectedMainObject() + "/" + _foundTable);
               }
               }
             }
             }
           }
           }
@@ -1022,7 +969,7 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
               _table = _aliases[_table];
               _table = _aliases[_table];
             }
             }
 
 
-            assist.getData(viewModel.assistContent().selectedMainObject() + "/" + _table);
+            assist.getData(assist.selectedMainObject() + "/" + _table);
 
 
           }
           }
 
 
@@ -1091,7 +1038,7 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
               }
               }
             }
             }
           }
           }
-          assist.getData(viewModel.assistContent().selectedMainObject());
+          assist.getData(assist.selectedMainObject());
         }
         }
       }
       }
 
 
@@ -1182,12 +1129,8 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
   };
   };
 
 
   var options = ${ options_json | n,unicode };
   var options = ${ options_json | n,unicode };
-  $.extend(options, {
-    assistVisible: $.totalStorage("sparkAssistVisible") != null ? $.totalStorage("sparkAssistVisible") : false
-  });
 
 
   viewModel = new EditorViewModel(${ notebooks_json | n,unicode }, options);
   viewModel = new EditorViewModel(${ notebooks_json | n,unicode }, options);
-  viewModel.assistContent(assist);
   ko.applyBindings(viewModel);
   ko.applyBindings(viewModel);
   viewModel.init();
   viewModel.init();
 
 
@@ -1246,70 +1189,6 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
     assist.getData(viewModel.assistContent().selectedMainObject(), force);
     assist.getData(viewModel.assistContent().selectedMainObject(), force);
   }
   }
 
 
-  function loadAssistMain(force) {
-    assist.options.onDataReceived = function (data) {
-      if (data.databases) {
-        viewModel.assistContent().mainObjects(data.databases);
-        if (force) {
-          loadAssistFirstLevel(force);
-        }
-        else if (viewModel.assistContent().mainObjects().length > 0 && !viewModel.assistContent().selectedMainObject()) {
-          viewModel.assistContent().selectedMainObject(viewModel.assistContent().mainObjects()[0]);
-          viewModel.assistSelectedMainObject(viewModel.assistContent().selectedMainObject());
-          loadAssistFirstLevel();
-        }
-      }
-    }
-    assist.options.onError = function (error) {
-      viewModel.assistContent().isLoading(false);
-    }
-    assist.getData(null, force);
-  }
-
-  if (assist.options.baseURL != ""){
-    loadAssistMain();
-  }
-  else {
-    viewModel.isAssistVisible(false);
-    viewModel.isAssistAvailable(false);
-  }
-
-  function reloadAssist() {
-    loadAssistMain(true);
-  }
-
-  function needsTruncation(level) {
-    return (level.name.length + level.type.length) > 20;
-  }
-
-  function secondLevelTitle(level) {
-    var _title = "";
-
-    if (level.comment && needsTruncation(level)) {
-      _title = level.name + " (" + level.type + "): " + level.comment;
-    } else if (needsTruncation(level)) {
-      _title = level.name + " (" + level.type + ")";
-    } else if (level.comment) {
-      _title = level.comment;
-    }
-    return _title;
-  }
-
-  function truncateSecondLevel(level) {
-    var escapeString = function (str) {
-      return $("<span>").text(str).html().trim()
-    }
-    if (needsTruncation(level)) {
-      return escapeString(level.name + " (" + level.type + ")").substr(0, 20) + "&hellip;";
-    }
-    return escapeString(level.name + " (" + level.type + ")");
-  }
-
-  function resizeAssist() {
-    $(".assist").parents(".span2").height($(".assist").height() + 100);
-    $(".assist-main").height($(window).height() - 230);
-  }
-
   function createDatatable(el, snippet) {
   function createDatatable(el, snippet) {
     $(el).addClass("dt");
     $(el).addClass("dt");
     var _dt = $(el).dataTable({
     var _dt = $(el).dataTable({
@@ -1642,34 +1521,6 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
     return _datum;
     return _datum;
   }
   }
 
 
-
-  function showTablePreview(table) {
-    var tableUrl = "/beeswax/api/table/" + viewModel.assistContent().selectedMainObject() + "/" + table;
-    $("#assistQuickLook").find(".tableName").text(table);
-    $("#assistQuickLook").find(".tableLink").attr("href", "/metastore/table/" + viewModel.assistContent().selectedMainObject() + "/" + table);
-    $("#assistQuickLook").find(".sample").empty("");
-    $("#assistQuickLook").attr("style", "width: " + ($(window).width() - 120) + "px;margin-left:-" + (($(window).width() - 80) / 2) + "px!important;");
-    $.ajax({
-      url: tableUrl,
-      data: {"sample": true},
-      beforeSend: function (xhr) {
-        xhr.setRequestHeader("X-Requested-With", "Hue");
-      },
-      dataType: "html",
-      success: function (data) {
-        $("#assistQuickLook").find(".loader").hide();
-        $("#assistQuickLook").find(".sample").html(data);
-      },
-      error: function (e) {
-        if (e.status == 500) {
-          $(document).trigger("error", "${ _('There was a problem loading the table preview.') }");
-          $("#assistQuickLook").modal("hide");
-        }
-      }
-    });
-    $("#assistQuickLook").modal("show");
-  }
-
   function redrawFixedHeaders() {
   function redrawFixedHeaders() {
       viewModel.notebooks().forEach(function (notebook) {
       viewModel.notebooks().forEach(function (notebook) {
         notebook.snippets().forEach(function (snippet) {
         notebook.snippets().forEach(function (snippet) {
@@ -1727,11 +1578,6 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
       }
       }
     });
     });
 
 
-    $(document).on("toggleAssist", function(){
-      $.totalStorage("sparkAssistVisible", viewModel.isAssistVisible());
-      resizeAssist();
-    });
-
     $(document).on("toggleLeftPanel", function(e, snippet){
     $(document).on("toggleLeftPanel", function(e, snippet){
       $("#snippet_" + snippet.id()).find(".chart").trigger("forceUpdate");
       $("#snippet_" + snippet.id()).find(".chart").trigger("forceUpdate");
       redrawFixedHeaders();
       redrawFixedHeaders();
@@ -1889,7 +1735,6 @@ ${ commonheader(_('Query'), app_name, user, "68px") | n,unicode }
     $(window).on("resize", function(){
     $(window).on("resize", function(){
       window.clearTimeout(_resizeTimeout);
       window.clearTimeout(_resizeTimeout);
       _resizeTimeout = window.setTimeout(function(){
       _resizeTimeout = window.setTimeout(function(){
-        resizeAssist();
         forceChartDraws();
         forceChartDraws();
       }, 200);
       }, 200);
     });
     });

+ 246 - 0
desktop/core/src/desktop/templates/ko_components.mako

@@ -0,0 +1,246 @@
+## 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.
+
+<%!
+from desktop import conf
+from desktop.lib.i18n import smart_unicode
+from django.utils.translation import ugettext as _
+%>
+
+<%def name="assistPanel()">
+  <template id="assist-panel-template">
+    <div class="assist-container" data-bind="visible: isAssistVisible() && isAssistAvailable(), css:{'span2': isAssistVisible(), 'hidden': !isAssistVisible() }">
+      <div id="assistQuickLook" class="modal hide fade">
+        <div class="modal-header">
+          <a href="#" class="close" data-dismiss="modal">&times;</a>
+          <h3>${_('Data sample for')} <span class="tableName"></span></h3>
+        </div>
+        <div class="modal-body" style="min-height: 100px">
+          <div class="loader">
+            <!--[if !IE]><!--><i class="fa fa-spinner fa-spin" style="font-size: 30px; color: #DDD"></i><!--<![endif]-->
+            <!--[if IE]><img src="${ static('desktop/art/spinner.gif') }"/><![endif]-->
+          </div>
+          <div class="sample"></div>
+        </div>
+        <div class="modal-footer">
+          <button class="btn btn-primary disable-feedback" data-dismiss="modal">${_('Ok')}</button>
+        </div>
+      </div>
+      <div class="span2 assist">
+
+        <a title="${_('Toggle Assist')}" class="pull-right pointer assist-action" data-bind="click: toggleAssist">
+          <i class="fa fa-chevron-left"></i>
+        </a>
+        <a title="${_('Manually refresh the table list')}" rel="tooltip" data-placement="top" class="pull-right pointer assist-action" data-bind="click: reloadAssist">
+          <i class="fa fa-refresh"></i>
+        </a>
+        <ul class="nav nav-list" style="border: none; padding: 0; background-color: #FFF; margin-bottom: 1px;">
+          <li class="nav-header">${_('database')}</li>
+        </ul>
+        <!-- ko if: assist.mainObjects().length > 0 -->
+        <select data-bind="options: assist.mainObjects, select2: { width: '100%', placeholder: '${ _("Choose a database...") }', update: assist.selectedMainObject}" class="input-medium" data-placeholder="${_('Choose a database...')}"></select>
+        <input type="text" placeholder="${ _('Table name...') }" style="width:90%; margin-top: 20px" data-bind="value: assist.filter, valueUpdate: 'afterkeydown'"/>
+
+        <div data-bind="visible: Object.keys(assist.firstLevelObjects()).length == 0">${_('The selected database has no tables.')}</div>
+        <ul data-bind="visible: Object.keys(assist.firstLevelObjects()).length > 0, foreach: assist.filteredFirstLevelObjects()" class="unstyled assist-main">
+          <li data-bind="event: { mouseover: function(){ $('#assistHover_' + $data).show(); }, mouseout: function(){ $('#assistHover_' + $data).hide(); } }">
+            <a href="javascript:void(0)" data-bind="attr: {'id': 'assistHover_' + $data}, click: $parent.showTablePreview" style="display: none; position: absolute; right: 10px; margin-left: auto; background-color: #FFF" class="preview-sample"><i class="fa fa-list" title="${'Preview Sample data'}" style="margin-left:5px"></i></a>
+            <a href="javascript:void(0)" data-bind="click: $parent.loadAssistSecondLevel, event: { 'dblclick': function(){ huePubSub.publish('assist.dblClickItem', $data); } }"><span data-bind="text: $data"></span></a>
+            <div data-bind="visible: $parent.assist.firstLevelObjects()[$data].loaded() && $parent.assist.firstLevelObjects()[$data].open()">
+              <ul data-bind="visible: $parent.assist.firstLevelObjects()[$data].items().length > 0, foreach: $parent.assist.firstLevelObjects()[$data].items()" class="unstyled">
+                <li><a data-bind="attr: {'title': $parents[1].secondLevelTitle($data)}" style="padding-left:10px" href="javascript:void(0)"><span data-bind="html: $parents[1].truncateSecondLevel($data), event: { 'dblclick': function() { huePubSub.publish('assist.dblClickItem', $data.name +', '); } }"></span></a></li>
+              </ul>
+            </div>
+          </li>
+        </ul>
+        <!-- /ko -->
+
+        <div id="navigatorLoader" class="center"  data-bind="visible: assist.isLoading">
+          <!--[if !IE]><!--><i class="fa fa-spinner fa-spin" style="font-size: 20px; color: #BBB"></i><!--<![endif]-->
+          <!--[if IE]><img src="${ static('desktop/art/spinner.gif') }"/><![endif]-->
+        </div>
+
+        <div class="center" data-bind="visible: assist.hasErrors">
+          ${ _('The database list cannot be loaded.') }
+        </div>
+      </div>
+    </div>
+  </template>
+
+  <script type="text/javascript" charset="utf-8">
+    (function() {
+      function AssistPanel(params) {
+        var self = this;
+
+        self.assist = params.assist;
+
+        self.isAssistVisible = params.isAssistVisible;
+        self.isAssistAvailable = params.isAssistAvailable;
+
+        self.isAssistVisible($.totalStorage(params.totalStorageId) != null && $.totalStorage(params.totalStorageId));
+
+        self.isAssistVisible.subscribe(function(newValue) {
+          $.totalStorage(params.totalStorageId, newValue);
+        });
+
+        self.toggleAssist = function () {
+          self.isAssistVisible(!self.isAssistVisible());
+        };
+
+        self.modalItem = ko.observable();
+
+        self.secondLevelTitle = function(level) {
+          var _title = "";
+
+          if (level.comment && needsTruncation(level)) {
+            _title = level.name + " (" + level.type + "): " + level.comment;
+          } else if (needsTruncation(level)) {
+            _title = level.name + " (" + level.type + ")";
+          } else if (level.comment) {
+            _title = level.comment;
+          }
+          return _title;
+        };
+
+        var needsTruncation = function(level) {
+          return (level.name.length + level.type.length) > 20;
+        };
+
+        self.truncateSecondLevel = function(level) {
+          var escapeString = function (str) {
+            return $("<span>").text(str).html().trim()
+          };
+          if (needsTruncation(level)) {
+            return escapeString(level.name + " (" + level.type + ")").substr(0, 20) + "&hellip;";
+          }
+          return escapeString(level.name + " (" + level.type + ")");
+        };
+
+        function resizeAssist() {
+          $(".assist").parents(".span2").height($(".assist").height() + 100);
+          $(".assist-main").height($(window).height() - 230);
+        }
+
+        $(window).on("resize", function(){
+          window.clearTimeout(_resizeTimeout);
+          _resizeTimeout = window.setTimeout(resizeAssist, 200);
+        });
+
+        self.loadAssistMain = function(force) {
+          self.assist.options.onDataReceived = function (data) {
+            if (data.databases) {
+              self.assist.mainObjects(data.databases);
+              if (force) {
+                self.loadAssistFirstLevel(force);
+              }
+              else if (self.assist.mainObjects().length > 0 && !self.assist.selectedMainObject()) {
+                self.assist.selectedMainObject(self.assist.mainObjects()[0]);
+                self.loadAssistFirstLevel();
+              }
+            }
+          };
+          self.assist.options.onError = function() {
+            self.assist.isLoading(false);
+          };
+          self.assist.getData(null, force);
+
+          self.assist.selectedMainObject.subscribe(function() {
+            self.loadAssistFirstLevel();
+          });
+        };
+
+        self.loadAssistFirstLevel = function(force) {
+          var self = this;
+          self.assist.options.onDataReceived = function (data) {
+            if (data.tables) {
+              var _obj = {};
+              data.tables.forEach(function (item) {
+                _obj[item] = {
+                  items: ko.observableArray([]),
+                  open: ko.observable(false),
+                  loaded: ko.observable(false)
+                }
+              });
+              self.assist.firstLevelObjects(_obj);
+            }
+            self.assist.isLoading(false);
+          };
+          self.assist.getData(self.assist.selectedMainObject(), force);
+        };
+
+        self.loadAssistSecondLevel = function(first) {
+          if (!self.assist.firstLevelObjects()[first].loaded()) {
+            self.assist.isLoading(true);
+            self.assist.options.onDataReceived = function (data) {
+              if (data.columns) {
+                var _cols = data.extended_columns ? data.extended_columns : data.columns;
+                self.assist.firstLevelObjects()[first].items(_cols);
+                self.assist.firstLevelObjects()[first].loaded(true);
+              }
+              self.assist.isLoading(false);
+            };
+            self.assist.getData(self.assist.selectedMainObject() + "/" + first);
+          }
+          self.assist.firstLevelObjects()[first].open(!self.assist.firstLevelObjects()[first].open());
+          window.setTimeout(self.resizeAssist, 100);
+        };
+
+        self.reloadAssist = function() {
+          self.loadAssistMain(true);
+        };
+
+        self.showTablePreview = function(table) {
+          var tableUrl = "/beeswax/api/table/" + self.assist.selectedMainObject() + "/" + table;
+          $("#assistQuickLook").find(".tableName").text(table);
+          $("#assistQuickLook").find(".tableLink").attr("href", "/metastore/table/" + self.assist.selectedMainObject() + "/" + table);
+          $("#assistQuickLook").find(".sample").empty("");
+          $("#assistQuickLook").attr("style", "width: " + ($(window).width() - 120) + "px;margin-left:-" + (($(window).width() - 80) / 2) + "px!important;");
+          $.ajax({
+            url: tableUrl,
+            data: {"sample": true},
+            beforeSend: function (xhr) {
+              xhr.setRequestHeader("X-Requested-With", "Hue");
+            },
+            dataType: "html",
+            success: function (data) {
+              $("#assistQuickLook").find(".loader").hide();
+              $("#assistQuickLook").find(".sample").html(data);
+            },
+            error: function (e) {
+              if (e.status == 500) {
+                $(document).trigger("error", "${ _('There was a problem loading the table preview.') }");
+                $("#assistQuickLook").modal("hide");
+              }
+            }
+          });
+          $("#assistQuickLook").modal("show");
+        };
+
+        if (self.assist.options.baseURL != ""){
+          self.loadAssistMain();
+        } else {
+          self.isAssistVisible(false);
+          self.isAssistAvailable(false);
+        }
+      }
+
+      ko.components.register('assist-panel', {
+        viewModel: AssistPanel,
+        template: { element: 'assist-panel-template' }
+      });
+    }());
+  </script>
+</%def>