瀏覽代碼

HUE-8007 [frontend] Show a dropdown for facet values in the inline autocomplete

Johan Ahlen 7 年之前
父節點
當前提交
a31c47f

File diff suppressed because it is too large
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/hue-embedded.css


File diff suppressed because it is too large
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/hue.css


File diff suppressed because it is too large
+ 0 - 0
desktop/core/src/desktop/static/desktop/css/hue3-extra.css


+ 14 - 1
desktop/core/src/desktop/static/desktop/less/hue-cross-version.less

@@ -426,7 +426,7 @@ ul.errorlist {
 
 .hue-drop-down-container {
   position: fixed;
-  z-index: 900;
+  z-index: 1061;
 
   &.open {
     position: absolute;
@@ -435,6 +435,19 @@ ul.errorlist {
   &.hue-drop-down-fixed {
     position: fixed !important;
   }
+
+  .dropdown-menu {
+    min-width: 190px;
+    max-width: 250px;
+    min-height: 34px;
+    max-height: 200px;
+    border-radius: 3px;
+    padding: 0;
+
+    .hue-inner-drop-down {
+      overflow-x: hidden;
+    }
+  }
 }
 
 .hue-drop-down-active span {

+ 93 - 46
desktop/core/src/desktop/templates/ko_components/ko_drop_down.mako

@@ -38,10 +38,10 @@ from desktop.views import _ko
     <input class="hue-drop-down-input" type="text" data-bind="textInput: filter, attr: { 'placeHolder': inputPlaceHolder }, visible: dropDownVisible, style: { color: filterEdited() ? '#000' : '#AAA', 'min-height': '22px', 'margin-left': '10px' }"/>
     <i class="fa fa-caret-down"></i>
     <!-- /ko -->
-    <div class="hue-drop-down-container" data-bind="css: { 'open' : dropDownVisible, 'hue-drop-down-fixed': fixedPosition }">
-      <div class="dropdown-menu" data-bind="visible: filteredEntries().length > 0" style="min-width: 190px; max-width: 250px; min-height: 34px; max-height: 200px;">
+    <div class="hue-drop-down-container" data-bind="css: { 'open' : dropDownVisible, 'hue-drop-down-fixed': fixedPosition }, dropDownKeyUp: { onEsc: onEsc, onEnter: onEnter, dropDownVisible: dropDownVisible }">
+      <div class="dropdown-menu" data-bind="visible: filteredEntries().length > 0, style: { 'overflow-y': !foreachVisible ? 'auto' : 'hidden' }">
         <!-- ko if: foreachVisible -->
-        <ul class="hue-inner-drop-down" style="overflow-x: hidden;" data-bind="foreachVisible: { data: filteredEntries, minHeight: 34, container: '.dropdown-menu' }">
+        <ul class="hue-inner-drop-down" data-bind="foreachVisible: { data: filteredEntries, minHeight: 34, container: '.dropdown-menu' }">
           <!-- ko if: typeof $data.divider !== 'undefined' && $data.divider -->
           <li class="divider"></li>
           <!-- /ko -->
@@ -51,7 +51,7 @@ from desktop.views import _ko
         </ul>
         <!-- /ko -->
         <!-- ko ifnot: foreachVisible -->
-        <ul class="hue-inner-drop-down" style="overflow-x: hidden;" data-bind="foreach: filteredEntries">
+        <ul class="hue-inner-drop-down" data-bind="foreach: filteredEntries">
           <!-- ko if: typeof $data.divider !== 'undefined' && $data.divider -->
           <li class="divider"></li>
           <!-- /ko -->
@@ -66,6 +66,86 @@ from desktop.views import _ko
 
   <script type="text/javascript">
     (function () {
+
+      ko.bindingHandlers.dropDownKeyUp = {
+        init: function (element, valueAccessor) {
+          var options = valueAccessor();
+          var onEsc = options.onEsc;
+          var onEnter = options.onEnter;
+          var onSelected = options.onSelected;
+          var dropDownVisible = options.dropDownVisible;
+
+          var keyUpTarget = options.keyUpTarget || window;
+
+          var keyUp = function (e) {
+            var $element = $(element);
+            var $dropDownMenu = $element.find('.dropdown-menu');
+            var $currentActive = $element.find('.hue-inner-drop-down > .active');
+            var activeTop = $currentActive.length !== 0 ? $currentActive.position().top : 0;
+            var activeHeight = $currentActive.length !== 0 ? $currentActive.outerHeight(true) : $element.find('.hue-inner-drop-down li:first-child').outerHeight(true);
+            var containerHeight = $dropDownMenu.innerHeight();
+            var containerScrollTop = $dropDownMenu.scrollTop();
+
+            $currentActive.removeClass('active');
+            if (e.keyCode === 27 && onEsc) {
+              onEsc();
+            } else if (e.keyCode === 38) {
+              // up
+              var $nextActive;
+              if ($currentActive.length !== 0 && $currentActive.prev().length !== 0) {
+                if (activeTop < containerScrollTop + activeHeight) {
+                  $dropDownMenu.scrollTop(activeTop - containerHeight / 2);
+                }
+                $nextActive = $currentActive.prev().addClass('active');
+              }
+              if (onSelected) {
+                onSelected($nextActive && $nextActive.length ? ko.dataFor($nextActive.prev()[0]) : undefined);
+              }
+            } else if (e.keyCode === 40) {
+              // down
+              var $nextActive;
+              if ($currentActive.length === 0) {
+                $nextActive = $element.find('.hue-inner-drop-down li:first-child').addClass('active');
+              } else if ($currentActive.next().length !== 0) {
+                if ((activeTop + activeHeight * 3) > containerHeight - containerScrollTop) {
+                  $dropDownMenu.scrollTop(activeTop - containerHeight / 2);
+                }
+                $nextActive = $currentActive.next().addClass('active');
+              } else {
+                $nextActive = $currentActive.addClass('active');
+              }
+
+              if (onSelected) {
+                onSelected($nextActive && $nextActive.length ? ko.dataFor($nextActive[0]) : undefined);
+              }
+            } else if (e.keyCode === 13) {
+              // enter
+              if ($currentActive.length > 0) {
+                $dropDownMenu.scrollTop(0)
+              }
+              if (onEnter) {
+                onEnter($currentActive.length ? ko.dataFor($currentActive[0]) : undefined);
+              }
+            } else {
+              $dropDownMenu.scrollTop(0)
+            }
+          };
+
+          var visibleSub = dropDownVisible.subscribe(function (newValue) {
+            if (newValue) {
+              $(keyUpTarget).on('keyup.hueDropDown', keyUp);
+            } else {
+              $(keyUpTarget).off('keyup.hueDropDown');
+            }
+          });
+
+          ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
+            visibleSub.dispose();
+            $(keyUpTarget).off('keyup.hueDropDown');
+          });
+        }
+      };
+
       var HueDropDown = function (params, element) {
         var self = this;
         self.dropDownVisible = ko.observable(!!params.showOnInit);
@@ -90,48 +170,15 @@ from desktop.views import _ko
           }
         };
 
-        var inputKeyup = function (e) {
-          var $currentActive = $(element).find('.hue-inner-drop-down > .active');
-          var activeTop = $currentActive.length !== 0 ? $currentActive.position().top : 0;
-          var activeHeight = $currentActive.length !== 0 ? $currentActive.outerHeight(true) : $(element).find('.hue-inner-drop-down li:first-child').outerHeight(true);
-          var containerHeight = $(element).find('.dropdown-menu').innerHeight();
-          var containerScrollTop = $(element).find('.dropdown-menu').scrollTop();
+        self.onEsc = function () {
+          self.dropDownVisible(false);
+        };
 
-          $currentActive.removeClass('active');
-          if (e.keyCode === 27) {
-            // esc
-            self.dropDownVisible(false);
-          } else if (e.keyCode === 38) {
-            // up
-            if ($currentActive.length !== 0 && $currentActive.prev().length !== 0) {
-              if (activeTop < containerScrollTop + activeHeight) {
-                $(element).find('.dropdown-menu').scrollTop(activeTop - containerHeight/2);
-              }
-              $currentActive.prev().addClass('active');
-            }
-          } else if (e.keyCode === 40) {
-            // down
-            if ($currentActive.length === 0) {
-              $(element).find('.hue-inner-drop-down li:first-child').addClass('active');
-            } else if ($currentActive.next().length !== 0) {
-              if ((activeTop + activeHeight *3) > containerHeight - containerScrollTop) {
-                $(element).find('.dropdown-menu').scrollTop(activeTop - containerHeight/2);
-              }
-              $currentActive.next().addClass('active');
-            } else {
-              $currentActive.addClass('active');
-            }
-          } else if (e.keyCode === 13) {
-            // enter
-            if ($currentActive.length > 0) {
-              self.value(ko.dataFor($currentActive[0]));
-              self.dropDownVisible(false);
-              $(element).find('.dropdown-menu').scrollTop(0)
-            }
-          } else {
-            $(element).find('.dropdown-menu').scrollTop(0)
-          }
+        self.onEnter = function (value) {
+          self.value(value);
+          self.dropDownVisible(false);
         };
+
         self.filter = ko.observable('');
 
         self.value.subscribe(function () {
@@ -144,21 +191,21 @@ from desktop.views import _ko
           self.filterEdited(true);
           $(element).find('.hue-inner-drop-down > .active').removeClass('.active');
         });
+
         self.dropDownVisible.subscribe(function (newValue) {
           self.filterEdited(false);
           if (newValue) {
             window.setTimeout(function () {
               self.filter('');
               $(window).on('click', closeOnOutsideClick);
-              $(element).find('.hue-drop-down-input').on('keyup', inputKeyup);
               $(element).find('.hue-drop-down-input').focus();
             }, 0);
           } else {
             $(element).find('.hue-inner-drop-down > .active').removeClass('.active');
             $(window).off('click', closeOnOutsideClick);
-            $(element).find('.hue-drop-down-input').off('keyup', inputKeyup);
           }
         });
+
         self.filteredEntries = ko.pureComputed(function () {
           if (self.filter() === '' || ! self.filterEdited()) {
             return ko.unwrap(self.entries);

+ 9 - 2
desktop/core/src/desktop/templates/ko_components/ko_global_search.mako

@@ -33,6 +33,7 @@ from desktop.views import _ko
         hasFocus: searchHasFocus,
         disableNavigation: true,
         showMagnify: true,
+        facetDropDownVisible: facetDropDownVisible,
         spin: loading,
         placeHolder: '${ _ko('Search data and saved documents...') if has_navigator(user) else _ko('Search saved documents...') }',
         querySpec: querySpec,
@@ -44,7 +45,7 @@ from desktop.views import _ko
       }
     } --><!-- /ko -->
     <!-- ko if: searchResultVisible()  -->
-    <!-- ko if: !loading() && searchResultCategories().length === 0 -->
+    <!-- ko if: !loading() && searchResultCategories().length === 0 && hasLoadedOnce() -->
     <div class="global-search-results global-search-empty" data-bind="onClickOutside: onResultClickOutside">
       <div>${ _('No results found.') }</div>
     </div>
@@ -131,6 +132,7 @@ from desktop.views import _ko
         self.fetchThrottle = -1;
 
         self.searchHasFocus = ko.observable(false);
+        self.facetDropDownVisible = ko.observable(false);
         self.querySpec = ko.observable();
         self.searchActive = ko.observable(false);
         self.searchResultVisible = ko.observable(false);
@@ -145,6 +147,8 @@ from desktop.views import _ko
           return self.loadingNav() || self.loadingDocs();
         });
 
+        self.hasLoadedOnce = ko.observable(false);
+
         self.initializeFacetValues();
 
         self.selectedResult = ko.pureComputed(function () {
@@ -227,7 +231,7 @@ from desktop.views import _ko
 
         // TODO: Consider attach/detach on focus
         $(document).keydown(function (event) {
-          if (!self.searchHasFocus() && !self.searchResultVisible()) {
+          if (self.facetDropDownVisible() || (!self.searchHasFocus() && !self.searchResultVisible())) {
             return;
           }
 
@@ -293,6 +297,7 @@ from desktop.views import _ko
         window.clearTimeout(self.fetchThrottle);
         self.cancelRunningRequests();
         self.searchResultVisible(false);
+        self.hasLoadedOnce(false);
         self.querySpec({
           query: '',
           facets: {},
@@ -435,6 +440,7 @@ from desktop.views import _ko
           self.loadingDocs(false);
           window.clearTimeout(clearDocsTimeout);
         }).done(function (data) {
+          self.hasLoadedOnce(true);
           var categories = [];
 
           var docCategory = {
@@ -488,6 +494,7 @@ from desktop.views import _ko
             self.loadingNav(false);
             window.clearTimeout(clearNavTimeout);
           }).done(function (data) {
+            self.hasLoadedOnce(true);
             var categories = [];
             var newCategories = {};
             data.results.forEach(function (result) {

+ 81 - 9
desktop/core/src/desktop/templates/ko_components/ko_inline_autocomplete.mako

@@ -46,6 +46,15 @@ from desktop.views import _ko
           css: { 'inline-autocomplete-magnify-input': showMagnify }">
       </div>
     </div>
+
+    <div class="hue-drop-down-container hue-drop-down-fixed" data-bind="event: { 'mousedown': facetDropDownMouseDown }, css: { 'open' : facetDropDownVisible() }, dropDownKeyUp: { onEsc: facetDropDownOnEsc, onEnter: facetDropDownOnEnter, onSelected: facetDropDownOnSelected, dropDownVisible: facetDropDownVisible }">
+      <div class="dropdown-menu" style="overflow-y: auto;" data-bind="visible: facetSuggestions().length > 0">
+        <ul class="hue-inner-drop-down" data-bind="foreach: facetSuggestions">
+          <li><a href="javascript:void(0)" data-bind="html: label, click: function () { $parent.facetClicked($data); }, clickBubble: false"></a></li>
+        </ul>
+      </div>
+    </div>
+
   </script>
 
   <script type="text/javascript">
@@ -84,6 +93,7 @@ from desktop.views import _ko
         self.knownFacetValues = params.knownFacetValues || {};
         self.uniqueFacets = !!params.uniqueFacets;
         self.disableNavigation = !!params.disableNavigation;
+        self.changedAfterFocus = false;
 
         self.searchInput = ko.observable('');
         self.suggestions = ko.observableArray();
@@ -93,6 +103,42 @@ from desktop.views import _ko
           self.selectedSuggestionIndex(0);
         });
 
+        self.facetSuggestions = ko.observableArray();
+        self.facetDropDownVisible = params.facetDropDownVisible || ko.observable(false);
+
+        self.facetClicked = function (facet) {
+          self.searchInput(facet.value);
+          self.facetDropDownVisible(false);
+        };
+
+        self.facetDropDownOnEnter = function (facet) {
+          if (facet) {
+            self.searchInput(facet.value);
+          }
+          self.facetDropDownVisible(false);
+        };
+
+        self.facetDropDownOnEsc = function () {
+          self.facetDropDownVisible(false);
+        };
+
+        self.facetDropDownOnSelected = function (facet) {
+          if (facet) {
+            var suggestions = self.suggestions();
+            for (var i = 0; i < suggestions.length; i++) {
+              if (facet.value === suggestions[i]) {
+                self.selectedSuggestionIndex(i);
+                break;
+              }
+            }
+          }
+        };
+
+        self.facetDropDownMouseDown = function (data, evt) {
+          evt.preventDefault(); // Prevent focus loss on the input when an entry is clicked
+          return false;
+        };
+
         self.inlineAutocomplete = ko.pureComputed(function () {
           if (!self.hasFocus() || self.suggestions().length === 0) {
             return '';
@@ -136,6 +182,7 @@ from desktop.views import _ko
           }
 
           if (newValue === '') {
+            self.changedAfterFocus = false;
             self.clearSuggestions();
             if (self.querySpec() && self.querySpec().query !== '') {
               self.querySpec({
@@ -147,6 +194,8 @@ from desktop.views import _ko
             return;
           }
 
+          self.changedAfterFocus = true;
+
           self.updateQuerySpec();
           self.autocomplete();
         });
@@ -159,7 +208,7 @@ from desktop.views import _ko
           if (!self.hasFocus()) {
             return;
           }
-          if (!self.disableNavigation && event.keyCode === 38 && self.suggestions().length) { // Up
+          if (!self.disableNavigation && event.keyCode === 38 && self.suggestions().length && !self.facetDropDownVisible()) { // Up
             if (self.selectedSuggestionIndex() === 0) {
               self.selectedSuggestionIndex(self.suggestions().length - 1);
             } else {
@@ -169,7 +218,7 @@ from desktop.views import _ko
             return;
           }
 
-          if (!self.disableNavigation && event.keyCode === 40 && self.suggestions().length) { // Down
+          if (!self.disableNavigation && event.keyCode === 40 && self.suggestions().length && !self.facetDropDownVisible()) { // Down
             if (self.selectedSuggestionIndex() === self.suggestions().length - 1) {
               self.selectedSuggestionIndex(0);
             } else {
@@ -179,6 +228,10 @@ from desktop.views import _ko
             return;
           }
           if (event.keyCode === 32 && event.ctrlKey) { // Ctrl-Space
+            if (!self.lastParseResult) {
+              self.updateQuerySpec();
+            }
+            self.changedAfterFocus = true;
             self.autocomplete();
             return;
           }
@@ -194,18 +247,22 @@ from desktop.views import _ko
         };
 
         self.disposals.push(function () {
-          $(document).off('keydown', onKeyDown);
+          $(document).off('keydown.inlineAutocomplete', onKeyDown);
         });
 
         var focusSub = self.hasFocus.subscribe(function (newVal) {
           if (!newVal) {
             self.clearSuggestions();
-            $(document).off('keydown', onKeyDown);
+            self.changedAfterFocus = false;
+            $(document).off('keydown.inlineAutocomplete', onKeyDown);
           } else if (self.searchInput() !== '') {
             self.autocomplete();
-            $(document).on('keydown', onKeyDown);
+            $(document).on('keydown.inlineAutocomplete', onKeyDown);
           } else {
-            $(document).on('keydown', onKeyDown);
+            $(document).on('keydown.inlineAutocomplete', onKeyDown);
+          }
+          if (!newVal && self.facetDropDownVisible()) {
+            self.facetDropDownVisible(false);
           }
         });
 
@@ -264,9 +321,12 @@ from desktop.views import _ko
 
       InlineAutocomplete.prototype.clearSuggestions = function () {
         var self = this;
-        if (self.suggestions().length > 0) {
+        if (self.suggestions().length) {
           self.suggestions([]);
         }
+        if (self.facetSuggestions().length) {
+          self.facetSuggestions([]);
+        }
       };
 
       InlineAutocomplete.prototype.dispose = function () {
@@ -295,6 +355,7 @@ from desktop.views import _ko
         }
 
         var newSuggestions = [];
+        var facetSuggestions = [];
         var partialLower = partial.toLowerCase();
         if (self.lastParseResult.suggestFacets) {
           var existingFacetIndex = {};
@@ -324,7 +385,13 @@ from desktop.views import _ko
           if (facetValues[self.lastParseResult.suggestFacetValues.toLowerCase()]) {
             getSortedFacets(facetValues[self.lastParseResult.suggestFacetValues.toLowerCase()]).forEach(function (value) {
               if (value.toLowerCase().indexOf(partialLower) === 0) {
-                newSuggestions.push(nonPartial + partial + value.substring(partial.length, value.length));
+                var fullValue = nonPartial + partial + value.substring(partial.length, value.length);
+                if (partial.length) {
+                  facetSuggestions.push({ label: '<b>' + partial + '</b>' + value.substring(partial.length), value: fullValue});
+                } else {
+                  facetSuggestions.push({ label: value, value: fullValue});
+                }
+                newSuggestions.push(fullValue);
               }
             });
           }
@@ -333,7 +400,12 @@ from desktop.views import _ko
         if (partial !== '' && self.lastParseResult.suggestResults) {
           newSuggestions = newSuggestions.concat(self.autocompleteFromEntries(nonPartial, partial));
         }
-
+        self.facetSuggestions(facetSuggestions);
+        if (self.changedAfterFocus && (facetSuggestions.length > 1 || (facetSuggestions.length === 1 && facetSuggestions[0].value !== self.searchInput()))) {
+          self.facetDropDownVisible(true);
+        } else if (self.facetDropDownVisible()) {
+          self.facetDropDownVisible(false);
+        }
         self.suggestions(newSuggestions);
       };
 

Some files were not shown because too many files changed in this diff