Browse Source

HUE-6815 [frontend] Extract the top search to a separate KO component

Johan Ahlen 8 years ago
parent
commit
18574ea

+ 8 - 154
desktop/core/src/desktop/templates/hue.mako

@@ -199,44 +199,7 @@ ${ hueIcons.symbols() }
         </div>
         <!-- /ko -->
 
-        <div class="search-container-top">
-          <input placeholder="${ _('Search data and saved documents...') }" type="text"
-            data-bind="autocomplete: {
-                source: searchAutocompleteSource,
-                itemTemplate: 'top-search-autocomp-item',
-                noMatchTemplate: 'top-search-autocomp-no-match',
-                classPrefix: 'nav-',
-                showOnFocus: true,
-                closeOnEnter: false,
-                onSelect: mainSearchSelect,
-                reopenPattern: /.*:$/
-              },
-              hasFocus: searchHasFocus,
-              clearable: { value: searchInput, onClear: function () { searchActive(false); huePubSub.publish('autocomplete.close'); } },
-              textInput: searchInput,
-              valueUpdate: 'afterkeydown'">
-        </div>
-
-        <script type="text/html" id="top-search-autocomp-item">
-          <a href="javascript:void(0);">
-            <div class="nav-autocomplete-item-link">
-              <div class="nav-search-result-icon"><i class="fa fa-fw" data-bind="css: icon"></i></div>
-              <div class="nav-search-result-text">
-                <div class="nav-search-result-header" data-bind="html: label, style: { 'padding-top': description ? 0 : '9px' }"></div>
-                <!-- ko if: description -->
-                <div class="nav-search-result-description" data-bind="html: description"></div>
-                <!-- /ko -->
-              </div>
-            </div>
-          </a>
-        </script>
-
-        <script type="text/html" id="top-search-autocomp-no-match">
-          <div class="nav-autocomplete-item-link" style="height: 30px;">
-            <div class="nav-autocomplete-empty">${ _('No match found') }</div>
-          </div>
-        </script>
-
+        <div class="search-container-top" data-bind="component: 'hue-global-search'"></div>
       </div>
 
       <div class="top-nav-right">
@@ -1202,25 +1165,6 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
 
     var topNavViewModel = (function (onePageViewModel) {
 
-      // TODO: Extract to common module (shared with nav search autocomplete)
-      var SEARCH_FACET_ICON = 'fa-tags';
-      var SEARCH_TYPE_ICONS = {
-        'DATABASE': 'fa-database',
-        'TABLE': 'fa-table',
-        'VIEW': 'fa-eye',
-        'FIELD': 'fa-columns',
-        'PARTITION': 'fa-th',
-        'SOURCE': 'fa-server',
-        'OPERATION': 'fa-cogs',
-        'OPERATION_EXECUTION': 'fa-cog',
-        'DIRECTORY': 'fa-folder-o',
-        'FILE': 'fa-file-o',
-        'SUB_OPERATION': 'fa-code-fork',
-        'COLLECTION': 'fa-search',
-        'HBASE': 'fa-th-large',
-        'HUE': 'fa-file-o'
-      };
-
       function TopNavViewModel (onePageViewModel) {
         var self = this;
         self.onePageViewModel = onePageViewModel;
@@ -1233,11 +1177,6 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
           self.leftNavVisible(false);
         });
 
-        self.apiHelper = ApiHelper.getInstance();
-        self.searchActive = ko.observable(false);
-        self.searchHasFocus = ko.observable(false);
-        self.searchInput = ko.observable();
-
         self.mainQuickCreateAction = ko.observable();
         self.quickCreateActions = ko.observableArray();
 
@@ -1361,93 +1300,8 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
           }
         };
         self.cluster = new ClusterPanelViewModel();
-
-        self.searchAutocompleteSource = function (request, callback) {
-          // TODO: Extract complete contents to common module (shared with nav search autocomplete)
-          var facetMatch = request.term.match(/([a-z]+):\s*(\S+)?$/i);
-          var isFacet = facetMatch !== null;
-          var partialMatch = isFacet ? null : request.term.match(/\S+$/);
-          var partial = isFacet && facetMatch[2] ? facetMatch[2] : (partialMatch ? partialMatch[0] : '');
-          var beforePartial = request.term.substring(0, request.term.length - partial.length);
-
-          self.apiHelper.globalSearchAutocomplete({
-            query:  request.term,
-            successCallback: function (data) {
-              var values = [];
-              var facetPartialRe = new RegExp(partial.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i'); // Protect for 'tags:*axe'
-
-              if (typeof data.resultsHuedocuments !== 'undefined') {
-                data.resultsHuedocuments.forEach(function (result) {
-                  values.push({ data: { label: result.hue_name, icon: SEARCH_TYPE_ICONS[result.type],  description: result.hue_description, link: result.link }, value: beforePartial + result.originalName });
-                });
-              }
-              if (values.length > 0) {
-                values.push({ divider: true });
-              }
-
-              if (isFacet && typeof data.facets !== 'undefined') { // Is typed facet, e.g. type: type:bla
-                var facetInQuery = facetMatch[1];
-                if (typeof data.facets[facetInQuery] !== 'undefined') {
-                  $.map(data.facets[facetInQuery], function (count, value) {
-                    if (facetPartialRe.test(value)) {
-                      values.push({ data: { label: facetInQuery + ':' + value, icon: SEARCH_FACET_ICON, description: count }, value: beforePartial + value})
-                    }
-                  });
-                }
-              } else {
-                if (typeof data.facets !== 'undefined') {
-                  Object.keys(data.facets).forEach(function (facet) {
-                    if (facetPartialRe.test(facet)) {
-                      if (Object.keys(data.facets[facet]).length > 0) {
-                        values.push({ data: { label: facet + ':', icon: SEARCH_FACET_ICON, description: $.map(data.facets[facet], function (key, value) { return value + ' (' + key + ')'; }).join(', ') }, value: beforePartial + facet + ':'});
-                      } else { // Potential facet from the list
-                        values.push({ data: { label: facet + ':', icon: SEARCH_FACET_ICON, description: '' }, value: beforePartial + facet + ':'});
-                      }
-                    } else if (partial.length > 0) {
-                      Object.keys(data.facets[facet]).forEach(function (facetValue) {
-                        if (facetValue.indexOf(partial) !== -1) {
-                          values.push({ data: { label: facet + ':' + facetValue, icon: SEARCH_FACET_ICON, description: facetValue }, value: beforePartial + facet + ':' + facetValue });
-                        }
-                      });
-                    }
-                  });
-                }
-              }
-
-              if (values.length > 0) {
-                values.push({ divider: true });
-              }
-              if (typeof data.results !== 'undefined') {
-                data.results.forEach(function (result) {
-                  values.push({ data: { label: result.hue_name, icon: SEARCH_TYPE_ICONS[result.type],  description: result.hue_description }, value: beforePartial + result.originalName });
-                });
-              }
-
-              if (values.length > 0 && values[values.length - 1].divider) {
-                values.pop();
-              }
-              if (values.length === 0) {
-                values.push({ noMatch: true });
-              }
-              callback(values);
-            },
-            silenceErrors: true,
-            errorCallback: function () {
-              callback([]);
-            }
-          });
-        };
       }
 
-      TopNavViewModel.prototype.mainSearchSelect = function (entry) {
-        if (entry.data && entry.data.link) {
-          huePubSub.publish('open.link', entry.data.link);
-        } else if (!/:\s*$/.test(entry.value)) {
-          huePubSub.publish('assist.show.sql');
-          huePubSub.publish('assist.db.search', entry.value);
-        }
-      };
-
       var topNavViewModel = new TopNavViewModel(onePageViewModel);
       ko.applyBindings(topNavViewModel, $('.top-nav')[0]);
 
@@ -1628,38 +1482,38 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
     });
 
     tour.addStep('welcome', {
-      text: '<b>${ _ko('Welcome to Hue 4!') }</b><br><br>${ _ko('We want to introduce you to the new interface. It takes less than a minute. Ready? ') }',
+      text: '<b>${ _ko('Welcome to Hue 4!') }</b><br><br>${ _ko('We want to introduce you to the new interface. It takes less than a minute. Ready? ') }'
     });
 
     tour.addStep('topnav', {
       text: '${ _ko('A new nav bar and big blue button!') }<br><br>${ _ko('Open Hue to your favorite app, select other apps from the blue button, do global search, and view notification panels at right.') }',
-      attachTo: '.navbar-default bottom',
+      attachTo: '.navbar-default bottom'
     });
 
     %if user.is_superuser:
       tour.addStep('admin', {
         text: '${ _ko('As a superuser, you can check system configuration from the username drop down and install sample data and jobs for your users.') }',
-        attachTo: '.top-nav-right .dropdown bottom',
+        attachTo: '.top-nav-right .dropdown bottom'
       });
     %endif
 
     tour.addStep('leftpanel', {
       text: '${ _ko('Discover data sources with the improved data assist panel. Remember to right-click for more!') }',
-      attachTo: '.left-panel right',
+      attachTo: '.left-panel right'
     });
 
     tour.addStep('pagecontent', {
       text: '${ _ko('This is the main attraction, where your selected app runs.') }<br>${ _ko('Hover on the app name to star it as your favorite application.') }',
-      attachTo: '.page-content center',
+      attachTo: '.page-content center'
     });
 
     tour.addStep('rightpanel', {
       text: '${ _ko('Some apps have a right panel with additional information to assist you in your data discovery.') }',
-      attachTo: '.right-panel left',
+      attachTo: '.right-panel left'
     });
 
     tour.addStep('bye', {
-      text: '${ _ko('This ends the tour. To see it again, click Welcome Tour from the username drop down.') }<br><br>${ _ko('And now go ') }<b>${ _ko('Query, Explore, Repeat') }</b>!',
+      text: '${ _ko('This ends the tour. To see it again, click Welcome Tour from the username drop down.') }<br><br>${ _ko('And now go ') }<b>${ _ko('Query, Explore, Repeat') }</b>!'
     });
 
     var closeTourOnEsc = function (e) {

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

@@ -548,4 +548,164 @@ from desktop.views import _ko
       });
     })();
   </script>
+
+  <script type="text/html" id="hue-global-search-template">
+    <input placeholder="${ _('Search data and saved documents...') }" type="text"
+           data-bind="autocomplete: {
+                source: searchAutocompleteSource,
+                itemTemplate: 'top-search-autocomp-item',
+                noMatchTemplate: 'top-search-autocomp-no-match',
+                classPrefix: 'nav-',
+                showOnFocus: true,
+                closeOnEnter: false,
+                onSelect: mainSearchSelect,
+                reopenPattern: /.*:$/
+              },
+              hasFocus: searchHasFocus,
+              clearable: { value: searchInput, onClear: function () { searchActive(false); huePubSub.publish('autocomplete.close'); } },
+              textInput: searchInput,
+              valueUpdate: 'afterkeydown'">
+  </script>
+
+  <script type="text/html" id="top-search-autocomp-item">
+    <a href="javascript:void(0);">
+      <div class="nav-autocomplete-item-link">
+        <div class="nav-search-result-icon"><i class="fa fa-fw" data-bind="css: icon"></i></div>
+        <div class="nav-search-result-text">
+          <div class="nav-search-result-header" data-bind="html: label, style: { 'padding-top': description ? 0 : '9px' }"></div>
+          <!-- ko if: description -->
+          <div class="nav-search-result-description" data-bind="html: description"></div>
+          <!-- /ko -->
+        </div>
+      </div>
+    </a>
+  </script>
+
+  <script type="text/html" id="top-search-autocomp-no-match">
+    <div class="nav-autocomplete-item-link" style="height: 30px;">
+      <div class="nav-autocomplete-empty">${ _('No match found') }</div>
+    </div>
+  </script>
+
+  <script type="text/javascript">
+    (function () {
+
+      var SEARCH_FACET_ICON = 'fa-tags';
+      var SEARCH_TYPE_ICONS = {
+        'DATABASE': 'fa-database',
+        'TABLE': 'fa-table',
+        'VIEW': 'fa-eye',
+        'FIELD': 'fa-columns',
+        'PARTITION': 'fa-th',
+        'SOURCE': 'fa-server',
+        'OPERATION': 'fa-cogs',
+        'OPERATION_EXECUTION': 'fa-cog',
+        'DIRECTORY': 'fa-folder-o',
+        'FILE': 'fa-file-o',
+        'SUB_OPERATION': 'fa-code-fork',
+        'COLLECTION': 'fa-search',
+        'HBASE': 'fa-th-large',
+        'HUE': 'fa-file-o'
+      };
+
+      var GlobalSearch = function (params) {
+        var self = this;
+        self.apiHelper = ApiHelper.getInstance();
+        self.searchHasFocus = ko.observable(false);
+        self.searchInput = ko.observable();
+        self.searchActive = ko.observable(false);
+      };
+
+      GlobalSearch.prototype.mainSearchSelect = function (entry) {
+        if (entry.data && entry.data.link) {
+          huePubSub.publish('open.link', entry.data.link);
+        } else if (!/:\s*$/.test(entry.value)) {
+          huePubSub.publish('assist.show.sql');
+          huePubSub.publish('assist.db.search', entry.value);
+        }
+      };
+
+      GlobalSearch.prototype.searchAutocompleteSource = function (request, callback) {
+        var apiHelper = ApiHelper.getInstance();
+        // TODO: Extract complete contents to common module (shared with nav search autocomplete)
+        var facetMatch = request.term.match(/([a-z]+):\s*(\S+)?$/i);
+        var isFacet = facetMatch !== null;
+        var partialMatch = isFacet ? null : request.term.match(/\S+$/);
+        var partial = isFacet && facetMatch[2] ? facetMatch[2] : (partialMatch ? partialMatch[0] : '');
+        var beforePartial = request.term.substring(0, request.term.length - partial.length);
+
+        apiHelper.globalSearchAutocomplete({
+          query:  request.term,
+          successCallback: function (data) {
+            var values = [];
+            var facetPartialRe = new RegExp(partial.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i'); // Protect for 'tags:*axe'
+
+            if (typeof data.resultsHuedocuments !== 'undefined') {
+              data.resultsHuedocuments.forEach(function (result) {
+                values.push({ data: { label: result.hue_name, icon: SEARCH_TYPE_ICONS[result.type],  description: result.hue_description, link: result.link }, value: beforePartial + result.originalName });
+              });
+            }
+            if (values.length > 0) {
+              values.push({ divider: true });
+            }
+
+            if (isFacet && typeof data.facets !== 'undefined') { // Is typed facet, e.g. type: type:bla
+              var facetInQuery = facetMatch[1];
+              if (typeof data.facets[facetInQuery] !== 'undefined') {
+                $.map(data.facets[facetInQuery], function (count, value) {
+                  if (facetPartialRe.test(value)) {
+                    values.push({ data: { label: facetInQuery + ':' + value, icon: SEARCH_FACET_ICON, description: count }, value: beforePartial + value})
+                  }
+                });
+              }
+            } else {
+              if (typeof data.facets !== 'undefined') {
+                Object.keys(data.facets).forEach(function (facet) {
+                  if (facetPartialRe.test(facet)) {
+                    if (Object.keys(data.facets[facet]).length > 0) {
+                      values.push({ data: { label: facet + ':', icon: SEARCH_FACET_ICON, description: $.map(data.facets[facet], function (key, value) { return value + ' (' + key + ')'; }).join(', ') }, value: beforePartial + facet + ':'});
+                    } else { // Potential facet from the list
+                      values.push({ data: { label: facet + ':', icon: SEARCH_FACET_ICON, description: '' }, value: beforePartial + facet + ':'});
+                    }
+                  } else if (partial.length > 0) {
+                    Object.keys(data.facets[facet]).forEach(function (facetValue) {
+                      if (facetValue.indexOf(partial) !== -1) {
+                        values.push({ data: { label: facet + ':' + facetValue, icon: SEARCH_FACET_ICON, description: facetValue }, value: beforePartial + facet + ':' + facetValue });
+                      }
+                    });
+                  }
+                });
+              }
+            }
+
+            if (values.length > 0) {
+              values.push({ divider: true });
+            }
+            if (typeof data.results !== 'undefined') {
+              data.results.forEach(function (result) {
+                values.push({ data: { label: result.hue_name, icon: SEARCH_TYPE_ICONS[result.type],  description: result.hue_description }, value: beforePartial + result.originalName });
+              });
+            }
+
+            if (values.length > 0 && values[values.length - 1].divider) {
+              values.pop();
+            }
+            if (values.length === 0) {
+              values.push({ noMatch: true });
+            }
+            callback(values);
+          },
+          silenceErrors: true,
+          errorCallback: function () {
+            callback([]);
+          }
+        });
+      };
+
+      ko.components.register('hue-global-search', {
+        viewModel: GlobalSearch,
+        template: {element: 'hue-global-search-template'}
+      });
+    })();
+  </script>
 </%def>