diff --git a/platform/search/res/templates/search.html b/platform/search/res/templates/search.html index e65ab072af..225df353b0 100644 --- a/platform/search/res/templates/search.html +++ b/platform/search/res/templates/search.html @@ -21,21 +21,16 @@ --> - +
- + Filtered by: {{ ngModel.filtersString }} - - +
- +
- +
Loading...
- +
+ ng-click="controller.loadMore()"> More Results
- +
- - \ No newline at end of file + + diff --git a/platform/search/src/controllers/SearchController.js b/platform/search/src/controllers/SearchController.js index 10cf056b4f..88c9663f51 100644 --- a/platform/search/src/controllers/SearchController.js +++ b/platform/search/src/controllers/SearchController.js @@ -26,146 +26,157 @@ */ define(function () { "use strict"; - + var INITIAL_LOAD_NUMBER = 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) { - // numResults is the amount of results to display. Will get increased. - // fullResults holds the most recent complete searchService response object - var numResults = INITIAL_LOAD_NUMBER, - fullResults = {hits: []}; - - // Scope variables are: - // Variables used only in SearchController: - // results, an array of searchResult objects - // loading, whether search() is loading - // ngModel.input, the text of the search query - // ngModel.search, a boolean of whether to display search or the tree - // 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); - } - } + var controller = this; + this.$scope = $scope; + this.searchService = searchService; + this.numberToDisplay = INITIAL_LOAD_NUMBER; + this.fullResults = []; + this.filteredResults = []; + this.$scope.results = []; + this.$scope.loading = false; + this.pendingQuery = undefined; + this.$scope.ngModel.filter = function () { + return controller.onFilterChange.apply(controller, arguments); }; } + + /** + * 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; });