From f4dbf91d8eaf32cede46d00e08e9d5f6dae84776 Mon Sep 17 00:00:00 2001 From: JohannesDoberer Date: Thu, 16 Jul 2020 12:54:36 +0200 Subject: [PATCH] Global search (#1496) --- core/fundamentalStyleClasses.js | 5 +- core/package.json | 2 +- core/src/App.html | 139 +++++++- core/src/core-api/globalsearch.js | 88 +++++ core/src/core-api/index.js | 3 + core/src/main.js | 27 ++ core/src/navigation/GlobalSearch.html | 313 ++++++++++++++++++ core/src/navigation/TopNav.html | 64 +++- docs/assets/globalsearch.jpg | Bin 0 -> 81653 bytes docs/global-search.md | 84 +++++ docs/luigi-core-api.md | 126 ++++++- docs/navigation-advanced.md | 1 + docs/navigation-parameters-reference.md | 51 +++ plugins/package-lock.json | 41 ++- scripts/package.json | 5 +- .../e2e/tests/1-angular/navigation.spec.js | 45 +++ test/e2e-test-application/package-lock.json | 132 ++++---- .../src/assets/customdocsearch.css | 68 ++++ test/e2e-test-application/src/index.html | 1 + .../src/luigi-config/extended/globalSearch.js | 180 ++++++++++ .../luigi-config/extended/lifecycle-hooks.js | 5 + .../src/luigi-config/extended/main.js | 4 +- website/fiddle/package-lock.json | 20 -- 23 files changed, 1293 insertions(+), 111 deletions(-) create mode 100644 core/src/core-api/globalsearch.js create mode 100644 core/src/navigation/GlobalSearch.html create mode 100644 docs/assets/globalsearch.jpg create mode 100644 docs/global-search.md create mode 100644 test/e2e-test-application/src/assets/customdocsearch.css create mode 100644 test/e2e-test-application/src/luigi-config/extended/globalSearch.js diff --git a/core/fundamentalStyleClasses.js b/core/fundamentalStyleClasses.js index ff0c4e3880..509e8eac65 100644 --- a/core/fundamentalStyleClasses.js +++ b/core/fundamentalStyleClasses.js @@ -24,8 +24,9 @@ module.exports = [ //'./node_modules/fundamental-styles/dist/form-message.css', //'./node_modules/fundamental-styles/dist/form-select.css', //'./node_modules/fundamental-styles/dist/inline-help.css', - //'./node_modules/fundamental-styles/dist/input.css', - //'./node_modules/fundamental-styles/dist/input-group.css', + './node_modules/fundamental-styles/dist/input.css', + './node_modules/fundamental-styles/dist/input-group.css', + //'./node_modules/fundamental-styles/dist/label.css', //'./node_modules/fundamental-styles/dist/layout-grid.css', //'./node_modules/fundamental-styles/dist/layout-panel.css', './node_modules/fundamental-styles/dist/link.css', diff --git a/core/package.json b/core/package.json index 24c8aa8a7c..45e2313c92 100644 --- a/core/package.json +++ b/core/package.json @@ -79,7 +79,7 @@ }, { "path": "./public-ie11/luigi-ie11.js", - "maxSize": "550 kB", + "maxSize": "560 kB", "compression": "none" }, { diff --git a/core/src/App.html b/core/src/App.html index fc2619da0c..2e286eb227 100644 --- a/core/src/App.html +++ b/core/src/App.html @@ -60,6 +60,15 @@ pathParams="{pathParams}" on:handleClick="{handleNavClick}" on:resizeTabNav="{onResizeTabNav}" + on:toggleSearch="{toggleSearch}" + on:closeSearchResult="{closeSearchResult}" + on:handleSearchNavigation="{handleSearchNavigation}" + bind:isSearchFieldVisible + bind:displaySearchResult + bind:displayCustomSearchResult + bind:searchResult + bind:inputElem + bind:luigiCustomSearchRenderer__slot /> {#if !(hideNav||hideSideNav)} { const iframeConf = config.iframe.luigi; @@ -476,6 +497,121 @@ setContext('handleNavigation', handleNavigation); + ////GLOBALSEARCH + + const checkSearchProvider = searchProvider => { + if (!searchProvider) { + console.warn('No search provider defined.'); + return false; + } else { + return true; + } + + // check for required searchProvider keys here. + }; + export const closeSearchField = () => { + if (checkSearchProvider(searchProvider)) { + isSearchFieldVisible = false; + } + }; + + export const openSearchField = () => { + if (checkSearchProvider(searchProvider)) { + if (inputElem) { + isSearchFieldVisible = true; + inputElem.focus(); + } + } + }; + + export const clearSearchField = () => { + if (checkSearchProvider(searchProvider)) { + if (inputElem) { + inputElem.value = ''; + closeSearchResult(); + } + } + }; + + export const toggleSearch = () => { + isSearchFieldVisible = !isSearchFieldVisible; + LuigiGlobalSearch.clearSearchField(); + }; + + export const getGlobalSearchString = () => { + if (checkSearchProvider(searchProvider)) { + if (inputElem) { + return inputElem.value; + } + } + }; + + export const setGlobalSearchString = searchString => { + if (checkSearchProvider(searchProvider)) { + if (inputElem) { + inputElem.value = searchString; + if (GenericHelpers.isFunction(searchProvider.onInput)) { + searchProvider.onInput(); + } else { + console.error( + 'onInput is not a function. Please check the global search configuration.' + ); + } + } + } + }; + + export const showSearchResult = arr => { + if (checkSearchProvider(searchProvider)) { + if (arr && arr.length > 0) { + if ( + GenericHelpers.isFunction(searchProvider.customSearchResultRenderer) + ) { + displayCustomSearchResult = true; + let searchApiObj = { + fireItemSelected: item => { + searchProvider.onSearchResultItemSelected(item); + } + }; + searchProvider.customSearchResultRenderer( + arr, + luigiCustomSearchRenderer__slot, + searchApiObj + ); + } else { + displaySearchResult = true; + searchResult = arr; + } + } else { + console.warn('Search result array is empty.'); + } + } + }; + + export const closeSearchResult = () => { + if (checkSearchProvider(searchProvider)) { + displaySearchResult = false; + searchResult = []; + if (luigiCustomSearchRenderer__slot) { + while (luigiCustomSearchRenderer__slot.lastElementChild) { + luigiCustomSearchRenderer__slot.removeChild( + luigiCustomSearchRenderer__slot.lastElementChild + ); + } + } + } + }; + + export const handleSearchNavigation = event => { + let node = event.detail.node; + let data = { + params: { + link: node.link, + nodeParams: node.params + } + }; + handleNavigation(data); + }; //// SPLIT VIEW export const openSplitView = (nodepath, settings) => { if (mfSplitView.displayed) { @@ -1098,6 +1234,7 @@ }; onMount(() => { + searchProvider = LuigiConfig.getConfigValue('globalSearch.searchProvider'); responsiveNavSetting = LuigiConfig.getConfigValue( 'settings.responsiveNavigation' ); diff --git a/core/src/core-api/globalsearch.js b/core/src/core-api/globalsearch.js new file mode 100644 index 0000000000..6a53aad08c --- /dev/null +++ b/core/src/core-api/globalsearch.js @@ -0,0 +1,88 @@ +/** + * Functions to use Luigi Global Search + * @name GlobalSearch + */ +class LuigiGlobalSearch { + /** + * Opens the global search field. + * @memberof GlobalSearch + * @since NEXTRELEASE + * @example Luigi.globalSearch().openSearchField(); + */ + openSearchField() { + Luigi.openSearchField(); + } + + /** + * Closes the global search field. + * @memberof GlobalSearch + * @since NEXTRELEASE + * @example Luigi.globalSearch().closeSearchField(); + */ + closeSearchField() { + Luigi.closeSearchField(); + } + + /** + * Clears the global search field. + * @memberof GlobalSearch + * @since NEXTRELEASE + * @example Luigi.globalSearch().clearSearchField(); + */ + clearSearchField() { + Luigi.clearSearchField(); + } + + /** + * Opens the global search result. By standard it is a popover. + * @memberof GlobalSearch + * @param {Array} searchResultItems + * @since NEXTRELEASE + * @example + * let searchResultItem = { + * pathObject: { + * link, + * params: {} + * }, + * label, + * description + * } + * + * Luigi.globalSearch().showSearchResult([searchResultItem1, searchResultItem2]); + */ + showSearchResult(searchResultItems) { + Luigi.showSearchResult(searchResultItems); + } + + /** + * Closes the global search result. By standard it is rendered as a popover. + * @memberof GlobalSearch + * @since NEXTRELEASE + * @example Luigi.globalSearch().closeSearchResult(); + */ + closeSearchResult() { + Luigi.closeSearchResult(); + } + + /** + * Gets the value of the search input field. + * @memberof GlobalSearch + * @since NEXTRELEASE + * @example Luigi.globalSearch().getSearchString(); + */ + getSearchString() { + return Luigi.getGlobalSearchString(); + } + + /** + * Sets the value of the search input field. + * @memberof GlobalSearch + * @param searchString search value + * @since NEXTRELEASE + * @example Luigi.globalSearch().setSearchString('searchString'); + */ + setSearchString(searchString) { + Luigi.setGlobalSearchString(searchString); + } +} +export const globalSearch = new LuigiGlobalSearch(); diff --git a/core/src/core-api/index.js b/core/src/core-api/index.js index 09e3e0bda4..7979f6d5a3 100644 --- a/core/src/core-api/index.js +++ b/core/src/core-api/index.js @@ -5,6 +5,7 @@ import { navigation } from './navigation'; import { i18n } from './i18n'; import { customMessages } from './custom-messages'; import { ux } from './ux'; +import { globalSearch } from './globalsearch'; export const LuigiConfig = config; export const LuigiAuth = auth; @@ -13,6 +14,7 @@ export const LuigiNavigation = navigation; export const LuigiI18N = i18n; export const LuigiCustomMessages = customMessages; export const LuigiUX = ux; +export const LuigiGlobalSearch = globalSearch; // Expose it window for user app to call Luigi.setConfig() window.Luigi = config; @@ -22,3 +24,4 @@ window.Luigi.navigation = () => navigation; window.Luigi.i18n = () => i18n; window.Luigi.customMessages = () => customMessages; window.Luigi.ux = () => ux; +window.Luigi.globalSearch = () => globalSearch; diff --git a/core/src/main.js b/core/src/main.js index 79d678bf49..0d8b6f21c9 100644 --- a/core/src/main.js +++ b/core/src/main.js @@ -66,6 +66,33 @@ const configReadyCallback = () => { return app.$$.ctx.showModal(settings); }; + Luigi.closeSearchField = () => { + return app.$$.ctx.closeSearchField(); + }; + Luigi.openSearchField = () => { + return app.$$.ctx.openSearchField(); + }; + + Luigi.getGlobalSearchString = () => { + return app.$$.ctx.getGlobalSearchString(); + }; + + Luigi.setGlobalSearchString = searchString => { + app.$$.ctx.setGlobalSearchString(searchString); + }; + + Luigi.showSearchResult = arr => { + return app.$$.ctx.showSearchResult(arr); + }; + + Luigi.closeSearchResult = () => { + app.$$.ctx.closeSearchResult(); + }; + + Luigi.clearSearchField = () => { + app.$$.ctx.clearSearchField(); + }; + Luigi.splitView = { openAsSplitView: (path, settings) => app.$$.ctx.openSplitView(path, settings), diff --git a/core/src/navigation/GlobalSearch.html b/core/src/navigation/GlobalSearch.html new file mode 100644 index 0000000000..5fbea516f4 --- /dev/null +++ b/core/src/navigation/GlobalSearch.html @@ -0,0 +1,313 @@ + +
+
+ +
+
+
+
+ +
+
+ + diff --git a/core/src/navigation/TopNav.html b/core/src/navigation/TopNav.html index 063f65cf1f..ed5df0f7b4 100644 --- a/core/src/navigation/TopNav.html +++ b/core/src/navigation/TopNav.html @@ -18,6 +18,19 @@
{#if !authorizationEnabled || isLoggedIn} + {#if isGlobalSearchAvailable} + + {/if}