UNPKG

terriajs

Version:

Geospatial data visualization platform.

243 lines (200 loc) 8.46 kB
'use strict'; /*global require*/ var inherit = require('../Core/inherit'); var runLater = require('../Core/runLater'); var SearchProviderViewModel = require('./SearchProviderViewModel'); var SearchResultViewModel = require('./SearchResultViewModel'); var defaultValue = require('terriajs-cesium/Source/Core/defaultValue'); var defined = require('terriajs-cesium/Source/Core/defined'); var when = require('terriajs-cesium/Source/ThirdParty/when'); var CatalogItemNameSearchProviderViewModel = function(options) { SearchProviderViewModel.call(this); options = defaultValue(options, defaultValue.EMPTY_OBJECT); this.terria = options.terria; this._searchInProgress = undefined; this.name = 'Catalogue Items'; this.maxResults = defaultValue(options.maxResults, 10000); }; inherit(SearchProviderViewModel, CatalogItemNameSearchProviderViewModel); CatalogItemNameSearchProviderViewModel.prototype.search = function(searchText) { function parseSearchFilters() { /* Filter search by type, eg 'is:wms fish' or '-is:wfs house'. Multiple 'is' clauses are or-ed. */ var isRE = /(^|\s)(-?)is:([a-zA-Z0-9_-]+)\b/i; while (searchText.match(isRE)) { if (!searchText.match(isRE)[2]) { searchFilters.typeIs.push(searchText.match(isRE)[3].toLowerCase()); } else { searchFilters.typeIsNot.push(searchText.match(isRE)[3].toLowerCase()); } searchText = searchText.replace(isRE, '').trim(); } /* Change number of search results: 'show:20' or 'show:all' */ var showRE = /\bshow:(all|[0-9]+)\b/i; while (searchText.match(showRE)) { searchFilters.maxResults = searchText.match(showRE)[1].toLowerCase(); if (searchFilters.maxResults === 'all') { searchFilters.maxResults = 10000; } else { searchFilters.maxResults = parseInt(searchFilters.maxResults, 10); } searchText = searchText.replace(showRE, '').trim(); } /* Filter by URL: 'url:landgate.wa' or '-url:geoserver.nicta.com.au' */ var urlRE = /(^|\s)(-?)url:([^ ]+)(\b|$)/i; while (searchText.match(urlRE)) { if (!searchText.match(urlRE)[2]) { searchFilters.urlMatches.push(searchText.match(urlRE)[3].toLowerCase()); } else { searchFilters.urlDoesNotMatch.push(searchText.match(urlRE)[3].toLowerCase()); } searchText = searchText.replace(urlRE, '').trim(); } } if (this._searchInProgress) { this._searchInProgress.cancel = true; this._searchInProgress = undefined; } if (!defined(searchText) || /^\s*$/.test(searchText)) { this.isSearching = false; this.searchResults.removeAll(); return; } var searchFilters = { typeIs: [], typeIsNot: [], urlMatches: [], urlDoesNotMatch: [] }; parseSearchFilters(); this.isSearching = true; this.searchResults.removeAll(); this.searchMessage = undefined; this.terria.analytics.logEvent('search', 'catalogue', searchText); var searchInProgress = this._searchInProgress = { cancel: false }; var path = []; var topLevelGroup = this.terria.catalog.group; var promise = findMatchingItemsRecursively(this, searchInProgress, new RegExp(searchText, 'i'), topLevelGroup, path, undefined, searchFilters); var that = this; return when(promise).then(function() { that.isSearching = false; if (searchInProgress.cancel) { return; } if (that.searchResults.length === 0) { that.searchMessage = 'Sorry, no catalogue items match your search query.'; } }); }; function itemMatchesFilters(item, searchFilters) { if (searchFilters.typeIs.length > 0 && searchFilters.typeIs.indexOf(item.type.toLowerCase()) < 0) { return false; } if (searchFilters.typeIsNot.length > 0 && searchFilters.typeIsNot.indexOf(item.type.toLowerCase()) >= 0) { return false; } if (!item.url) { // if no URL, it can't match any positive filters, and can't fail by matching any negative ones. return searchFilters.urlMatches.length === 0; } var r = true; // multiple -url: filters are and-ed searchFilters.urlDoesNotMatch.forEach(function(e) { // we just do simple string matching, not regex if (item.url.toLowerCase().indexOf(e) >= 0) { r = false; } }); if (!r) { return false; } if (searchFilters.urlMatches.length === 0) { return true; } r = false; // multiple url: filters are or-ed searchFilters.urlMatches.forEach(function(e) { // we just do simple string matching, not regex if (item.url.toLowerCase().indexOf(e) >= 0) { r = true; } }); return r; } function findMatchingItemsRecursively(viewModel, searchInProgress, searchExpression, group, path, promise, searchFilters) { path.push(group); if (!defined (searchFilters)) { searchFilters = {}; } var items = group.items; var maxResults = (defined(searchFilters.maxResults) ? searchFilters.maxResults : viewModel.maxResults); for (var i = 0; !searchInProgress.cancel && viewModel.searchResults.length < maxResults && i < items.length; ++i) { var item = items[i]; if (item.isHidden) { continue; } // Match non-top-level items whose name contain the search text. if (searchExpression.test(item.name) && itemMatchesFilters(item, searchFilters)) { addSearchResult(viewModel.searchResults, item, path); } if (defined(item.items)) { promise = loadAndSearchGroup(viewModel, item, searchInProgress, searchExpression, path, promise, searchFilters); } } path.pop(); return promise; } function loadAndSearchGroup(viewModel, group, searchInProgress, searchExpression, path, promise, searchFilters) { path = path.slice(); // Let a previous load (represented by 'promise') finish first. return when(promise).then(function() { if (searchInProgress.cancel) { return; } return runLater(function() { if (searchInProgress.cancel) { return; } var loadPromise = group.load(); if (defined(loadPromise) && group.isLoading) { return loadPromise.then(function() { return findMatchingItemsRecursively(viewModel, searchInProgress, searchExpression, group, path, undefined, searchFilters); }).otherwise(ignoreLoadErrors); } else { return findMatchingItemsRecursively(viewModel, searchInProgress, searchExpression, group, path, undefined, searchFilters); } }); }); } function ignoreLoadErrors() { } function addSearchResult(searchResults, item, path) { // Get the index of an existing search result that refers to the same catalog item (or -1) var index = -1; for (var j = 0; j < searchResults.length; ++j) { if (item === searchResults[j].catalogItem) { index = j; break; } } // If a search result for item already exists, modify the tooltip of that search result var prefix = 'In multiple locations including: '; if (index >= 0) { if (searchResults[index].tooltip.indexOf(prefix) !== 0) { searchResults[index].tooltip = searchResults[index].tooltip.replace(/^In /, prefix); } } else { // Otherwise, create a new search result searchResults.push(new SearchResultViewModel({ name: item.name, isImportant: true, catalogItem: item, tooltip: pathToTooltip(path) })); } } function pathToTooltip(path) { var result = 'In Data Catalogue'; // Start at 1 to skip "Root Group" for (var i = 1; i < path.length; ++i) { result += ' -> ' + path[i].name; } return result; } module.exports = CatalogItemNameSearchProviderViewModel;