mirror of
https://github.com/nasa/openmct.git
synced 2025-06-17 06:38:17 +00:00
[Search] Rewrite search controller, tidy
Rewrite the search controller, making numerous changes and using prototypical style. First, the search controller immediately hides previous results when a new search is started. Secondly, the search controller ensures that search results displayed match the currently entered query, preventing race conditions. Finally, the search controller uses a poor filtering option that means it may not display all results.
This commit is contained in:
@ -21,21 +21,16 @@
|
|||||||
-->
|
-->
|
||||||
<div class="search"
|
<div class="search"
|
||||||
ng-controller="SearchController as controller">
|
ng-controller="SearchController as controller">
|
||||||
|
|
||||||
<!-- Search bar -->
|
<!-- Search bar -->
|
||||||
<div class="search-bar"
|
<div class="search-bar"
|
||||||
ng-controller="ClickAwayController as toggle">
|
ng-controller="ClickAwayController as toggle">
|
||||||
|
|
||||||
<!-- Input field -->
|
<!-- Input field -->
|
||||||
<input class="search-input"
|
<input class="search-input"
|
||||||
type="text"
|
type="text"
|
||||||
ng-model="ngModel.input"
|
ng-model="ngModel.input"
|
||||||
ng-keyup="controller.search()" />
|
ng-keyup="controller.search()" />
|
||||||
<!--mct-control key="'textfield'"
|
|
||||||
class="search-input"
|
|
||||||
ng-model="ngModel.input"
|
|
||||||
ng-keyup="controller.search()">
|
|
||||||
</mct-control-->
|
|
||||||
|
|
||||||
<!-- Search icon -->
|
<!-- Search icon -->
|
||||||
<!-- ui symbols for search are 'd' and 'M' -->
|
<!-- ui symbols for search are 'd' and 'M' -->
|
||||||
@ -43,20 +38,20 @@
|
|||||||
ng-class="{content: !(ngModel.input === '' || ngModel.input === undefined)}">
|
ng-class="{content: !(ngModel.input === '' || ngModel.input === undefined)}">
|
||||||
M
|
M
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Clear icon/button 'x' -->
|
<!-- Clear icon/button 'x' -->
|
||||||
<a class="ui-symbol clear-icon"
|
<a class="ui-symbol clear-icon"
|
||||||
ng-class="{content: !(ngModel.input === '' || ngModel.input === undefined)}"
|
ng-class="{content: !(ngModel.input === '' || ngModel.input === undefined)}"
|
||||||
ng-click="ngModel.input = ''; controller.search()">
|
ng-click="ngModel.input = ''; controller.search()">
|
||||||

|

|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Menu icon/button 'v' -->
|
<!-- Menu icon/button 'v' -->
|
||||||
<a class="ui-symbol menu-icon"
|
<a class="ui-symbol menu-icon"
|
||||||
ng-click="toggle.toggle()">
|
ng-click="toggle.toggle()">
|
||||||
v
|
v
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Menu -->
|
<!-- Menu -->
|
||||||
<mct-representation key="'search-menu'"
|
<mct-representation key="'search-menu'"
|
||||||
class="menu-element search-menu-holder"
|
class="menu-element search-menu-holder"
|
||||||
@ -65,27 +60,24 @@
|
|||||||
ng-click="toggle.setState(true)">
|
ng-click="toggle.setState(true)">
|
||||||
</mct-representation>
|
</mct-representation>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Active filter display -->
|
<!-- Active filter display -->
|
||||||
<div class="active-filter-display"
|
<div class="active-filter-display"
|
||||||
ng-class="{off: ngModel.filtersString === '' || ngModel.filtersString === undefined || !ngModel.search}"
|
ng-class="{off: ngModel.filtersString === '' || ngModel.filtersString === undefined || !ngModel.search}"
|
||||||
ng-controller="SearchMenuController as menuController">
|
ng-controller="SearchMenuController as menuController">
|
||||||
|
|
||||||
<a class="ui-symbol clear-filters-icon"
|
<a class="ui-symbol clear-filters-icon"
|
||||||
ng-click="ngModel.checkAll = true; menuController.checkAll()">
|
ng-click="ngModel.checkAll = true; menuController.checkAll()">
|
||||||

|

|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Filtered by: {{ ngModel.filtersString }}
|
Filtered by: {{ ngModel.filtersString }}
|
||||||
|
|
||||||
<!--div class="filter-options">
|
|
||||||
Filtered by: {{ ngModel.filtersString }}
|
|
||||||
</div-->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- This div exists to determine scroll bar location -->
|
<!-- This div exists to determine scroll bar location -->
|
||||||
<div class="search-scroll abs">
|
<div class="search-scroll abs">
|
||||||
|
|
||||||
<!-- Results list -->
|
<!-- Results list -->
|
||||||
<div class="results">
|
<div class="results">
|
||||||
<mct-representation key="'search-item'"
|
<mct-representation key="'search-item'"
|
||||||
@ -103,14 +95,14 @@
|
|||||||
<span class="title-label">Loading...</span>
|
<span class="title-label">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Load more button -->
|
<!-- Load more button -->
|
||||||
<div ng-if="controller.areMore()">
|
<div ng-if="controller.areMore()">
|
||||||
<a class="load-more-button s-btn vsm"
|
<a class="load-more-button s-btn vsm"
|
||||||
ng-click="controller.loadMore()">
|
ng-click="controller.loadMore()">
|
||||||
More Results
|
More Results
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,146 +26,157 @@
|
|||||||
*/
|
*/
|
||||||
define(function () {
|
define(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var INITIAL_LOAD_NUMBER = 20,
|
var INITIAL_LOAD_NUMBER = 20,
|
||||||
LOAD_INCREMENT = 20;
|
LOAD_INCREMENT = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for search in Tree View.
|
||||||
|
*
|
||||||
|
* Filtering is currently buggy; it filters after receiving results from
|
||||||
|
* search providers, the downside of this is that it requires search
|
||||||
|
* providers to provide objects for all possible results, which is
|
||||||
|
* potentially a hit to persistence, thus can be very very slow.
|
||||||
|
*
|
||||||
|
* Ideally, filtering should be handled before loading objects from the persistence
|
||||||
|
* store, the downside to this is that filters must be applied to object
|
||||||
|
* models, not object instances.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param $scope
|
||||||
|
* @param searchService
|
||||||
|
*/
|
||||||
function SearchController($scope, searchService) {
|
function SearchController($scope, searchService) {
|
||||||
// numResults is the amount of results to display. Will get increased.
|
var controller = this;
|
||||||
// fullResults holds the most recent complete searchService response object
|
this.$scope = $scope;
|
||||||
var numResults = INITIAL_LOAD_NUMBER,
|
this.searchService = searchService;
|
||||||
fullResults = {hits: []};
|
this.numberToDisplay = INITIAL_LOAD_NUMBER;
|
||||||
|
this.fullResults = [];
|
||||||
// Scope variables are:
|
this.filteredResults = [];
|
||||||
// Variables used only in SearchController:
|
this.$scope.results = [];
|
||||||
// results, an array of searchResult objects
|
this.$scope.loading = false;
|
||||||
// loading, whether search() is loading
|
this.pendingQuery = undefined;
|
||||||
// ngModel.input, the text of the search query
|
this.$scope.ngModel.filter = function () {
|
||||||
// ngModel.search, a boolean of whether to display search or the tree
|
return controller.onFilterChange.apply(controller, arguments);
|
||||||
// Variables used also in SearchMenuController:
|
|
||||||
// ngModel.filter, the function filter defined below
|
|
||||||
// ngModel.types, an array of type objects
|
|
||||||
// ngModel.checked, a dictionary of which type filter options are checked
|
|
||||||
// ngModel.checkAll, a boolean of whether to search all types
|
|
||||||
// ngModel.filtersString, a string list of what filters on the results are active
|
|
||||||
$scope.results = [];
|
|
||||||
$scope.loading = false;
|
|
||||||
|
|
||||||
|
|
||||||
// Filters searchResult objects by type. Allows types that are
|
|
||||||
// checked. (ngModel.checked['typekey'] === true)
|
|
||||||
// If hits is not provided, will use fullResults.hits
|
|
||||||
function filter(hits) {
|
|
||||||
var newResults = [],
|
|
||||||
i = 0;
|
|
||||||
|
|
||||||
if (!hits) {
|
|
||||||
hits = fullResults.hits;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If checkAll is checked, search everything no matter what the other
|
|
||||||
// checkboxes' statuses are. Otherwise filter the search by types.
|
|
||||||
if ($scope.ngModel.checkAll) {
|
|
||||||
newResults = fullResults.hits.slice(0, numResults);
|
|
||||||
} else {
|
|
||||||
while (newResults.length < numResults && i < hits.length) {
|
|
||||||
// If this is of an acceptable type, add it to the list
|
|
||||||
if ($scope.ngModel.checked[hits[i].object.getModel().type]) {
|
|
||||||
newResults.push(fullResults.hits[i]);
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.results = newResults;
|
|
||||||
return newResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make function accessible from SearchMenuController
|
|
||||||
$scope.ngModel.filter = filter;
|
|
||||||
|
|
||||||
// For documentation, see search below
|
|
||||||
function search(maxResults) {
|
|
||||||
var inputText = $scope.ngModel.input;
|
|
||||||
|
|
||||||
if (inputText !== '' && inputText !== undefined) {
|
|
||||||
// We are starting to load.
|
|
||||||
$scope.loading = true;
|
|
||||||
|
|
||||||
// Update whether the file tree should be displayed
|
|
||||||
// Hide tree only when starting search
|
|
||||||
$scope.ngModel.search = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!maxResults) {
|
|
||||||
// Reset 'load more'
|
|
||||||
numResults = INITIAL_LOAD_NUMBER;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the query
|
|
||||||
searchService.query(inputText, maxResults).then(function (result) {
|
|
||||||
// Store all the results before splicing off the front, so that
|
|
||||||
// we can load more to display later.
|
|
||||||
fullResults = result;
|
|
||||||
$scope.results = filter(result.hits);
|
|
||||||
|
|
||||||
// Update whether the file tree should be displayed
|
|
||||||
// Reveal tree only when finishing search
|
|
||||||
if (inputText === '' || inputText === undefined) {
|
|
||||||
$scope.ngModel.search = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we are done loading.
|
|
||||||
$scope.loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Search the filetree. Assumes that any search text will
|
|
||||||
* be in ngModel.input
|
|
||||||
*
|
|
||||||
* @param maxResults (optional) The maximum number of results
|
|
||||||
* that this function should return. If not provided, search
|
|
||||||
* service default will be used.
|
|
||||||
*/
|
|
||||||
search: search,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks to see if there are more search results to display. If the answer is
|
|
||||||
* unclear, this function will err toward saying that there are more results.
|
|
||||||
*/
|
|
||||||
areMore: function () {
|
|
||||||
var i;
|
|
||||||
|
|
||||||
// Check to see if any of the not displayed results are of an allowed type
|
|
||||||
for (i = numResults; i < fullResults.hits.length; i += 1) {
|
|
||||||
if ($scope.ngModel.checkAll || $scope.ngModel.checked[fullResults.hits[i].object.getModel().type]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of the ones at hand are correct, there still may be more if we
|
|
||||||
// re-search with a larger maxResults
|
|
||||||
return fullResults.hits.length < fullResults.total;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the number of search results to display, and then
|
|
||||||
* loads them, adding to the displayed results.
|
|
||||||
*/
|
|
||||||
loadMore: function () {
|
|
||||||
numResults += LOAD_INCREMENT;
|
|
||||||
|
|
||||||
if (numResults > fullResults.hits.length && fullResults.hits.length < fullResults.total) {
|
|
||||||
// Resend the query if we are out of items to display, but there are more to get
|
|
||||||
search(numResults);
|
|
||||||
} else {
|
|
||||||
// Otherwise just take from what we already have
|
|
||||||
$scope.results = filter(fullResults.hits);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are more results than currently displayed for the
|
||||||
|
* for the current query and filters.
|
||||||
|
*/
|
||||||
|
SearchController.prototype.areMore = function () {
|
||||||
|
return this.$scope.results.length < this.filteredResults.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display more results for the currently displayed query and filters.
|
||||||
|
*/
|
||||||
|
SearchController.prototype.loadMore = function () {
|
||||||
|
this.numberToDisplay += LOAD_INCREMENT;
|
||||||
|
this.updateResults();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the query string specified in scope.
|
||||||
|
*/
|
||||||
|
SearchController.prototype.search = function () {
|
||||||
|
var inputText = this.$scope.ngModel.input,
|
||||||
|
controller = this;
|
||||||
|
|
||||||
|
this.clearResults();
|
||||||
|
|
||||||
|
if (inputText) {
|
||||||
|
this.$scope.loading = true;
|
||||||
|
this.$scope.ngModel.search = true;
|
||||||
|
} else {
|
||||||
|
this.pendingQuery = undefined;
|
||||||
|
this.$scope.ngModel.search = false;
|
||||||
|
this.$scope.loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.pendingQuery === inputText) {
|
||||||
|
return; // don't issue multiple queries for the same term.
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pendingQuery = inputText;
|
||||||
|
|
||||||
|
this
|
||||||
|
.searchService
|
||||||
|
.query(inputText, 60) // TODO: allow filter in search service.
|
||||||
|
.then(function (results) {
|
||||||
|
if (controller.pendingQuery !== inputText) {
|
||||||
|
return; // another query in progress, so skip this one.
|
||||||
|
}
|
||||||
|
controller.onSearchComplete(results);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refilter results and update visible results when filters have changed.
|
||||||
|
*/
|
||||||
|
SearchController.prototype.onFilterChange = function () {
|
||||||
|
this.filter();
|
||||||
|
this.updateVisibleResults();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter `fullResults` based on currenly active filters, storing the result
|
||||||
|
* in `filteredResults`.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
SearchController.prototype.filter = function () {
|
||||||
|
var includeTypes = this.$scope.ngModel.checked;
|
||||||
|
|
||||||
|
if (this.$scope.ngModel.checkAll) {
|
||||||
|
this.filteredResults = this.fullResults;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filteredResults = this.fullResults.filter(function (hit) {
|
||||||
|
return includeTypes[hit.object.getModel().type];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the search results.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
SearchController.prototype.clearResults = function () {
|
||||||
|
this.$scope.results = [];
|
||||||
|
this.fullResults = [];
|
||||||
|
this.filteredResults = [];
|
||||||
|
this.numberToDisplay = INITIAL_LOAD_NUMBER;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update search results from given `results`.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
SearchController.prototype.onSearchComplete = function (results) {
|
||||||
|
this.fullResults = results.hits;
|
||||||
|
this.filter();
|
||||||
|
this.updateVisibleResults();
|
||||||
|
this.$scope.loading = false;
|
||||||
|
this.pendingQuery = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update visible results from filtered results.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
SearchController.prototype.updateVisibleResults = function () {
|
||||||
|
this.$scope.results =
|
||||||
|
this.filteredResults.slice(0, this.numberToDisplay);
|
||||||
|
};
|
||||||
|
|
||||||
return SearchController;
|
return SearchController;
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user