HTML:
CSS: /* Homepage search container */ .homepage-search-container { padding: 50px 0; background-color: transparent; /* No additional background */ } /* Search bar container styling */ .search-bar-container { display: flex; /* Use flexbox for alignment */ align-items: center; /* Center vertically */ position: relative; /* Position relative for absolute child positioning */ } /* Search input styling */ .search-input { width: 60%; /* Adjust width for better separation */ height: 50px; padding: 10px 40px; /* Add padding for the icon */ border: 2px solid #ccc; /* Add border styling */ border-radius: 8px; /* Slightly rounded corners */ font-size: 16px; /* Adjust font size if needed */ background-color: white; /* White background for input */ color: #333; /* Text color for input */ outline: none; /* Remove outline on focus */ box-sizing: border-box; /* Include padding and border in element's total width and height */ } /* Placeholder text color */ .search-input::placeholder { color: #999; /* Placeholder text color */ } /* Search icon styling */ .search-icon { position: absolute; /* Position absolutely within the relative container */ left: 10px; /* Adjust left positioning as needed */ font-size: 20px; /* Adjust icon size */ color: #999; /* Icon color */ } /* Search button styling */ .search-button { height: 50px; width: 120px; margin-left: 10px; /* Add space between the input and button */ background-color: DarkGray; /* Change button background color to white */ border: 2px solid #ccc; /* Border styling to match input */ border-radius: 8px; /* Rounded corners */ color: #454545; /* Button text color (blue) */ font-size: 16px; cursor: pointer; /* Cursor changes to pointer */ transition: background-color 0.3s ease; /* Transition for hover effect */ } /* Button hover effect */ .search-button:hover { background-color: #f0f0f0; /* Subtle hover effect */ } /* Typeahead-specific CSS (existing) */ ul.dropdown-menu { min-width: 100%; max-height: 200px; overflow-y: auto; border-radius: 0px 0px 4px 4px; margin: 0px; } ul.dropdown-menu a.ta-item { line-height: 20px; } ul.dropdown-menu i.ta-icon, i.ta-img { width: 20px; height: 20px; background-size: contain; display: inline-block; background-repeat: no-repeat; background-position: center center; text-align: center; line-height: 20px; vertical-align: bottom; margin-right: 8px; } input[name="q"] { color: black; } .aisearch { --classicsponlydonotuse--rem-multipy: 1.4; } button[name="search"]:focus { outline: 2px solid $input-border-focus !important; outline-offset: -4px !important; } CLient Script: function ($http, $filter, $location,spAriaUtil, $window, $scope, $rootScope, spAriaFocusManager, snAnalytics, spAISearchResults) { var c = this; // Code common for Zing and AI Search start $scope.$on('$locationChangeSuccess', onLocationChangeSuccess); function setSearchTerm(newUrl, oldUrl) { try { var oldQuery = new URL(oldUrl).searchParams.get("q"); var newQuery = new URL(newUrl).searchParams.get("q"); if (oldQuery === newQuery) return; if (c.data.aisEnabled) c.data.q = newQuery; else c.searchTerm = newQuery; } catch (e) {} } var regExpr = /[?&](t=[^&]+)/; function onLocationChangeSuccess(event, newUrl, oldUrl) { if (!c.data.aisEnabled && c.searchSourceChanged(newUrl, oldUrl)) { var newUrlParams = newUrl.match(regExpr); if (!newUrlParams) c.searchType = null; else c.searchType = newUrlParams[1].substring(2); } setSearchTerm(newUrl, oldUrl); } // Code common for Zing and AI Search end if (c.data.aisEnabled) intializeAISearch(); else initializeZingSearch(); function intializeAISearch() { // AI Search functions c.aisSubmit = function(payload) { var shouldReloadPage = c.data.refreshPageOnSearch && $location.search().id === 'search'; if (payload.searchTerm) { var newUrlObj = { id: 'search', spa: '1', q: payload.searchTerm, disableAllSuggestions: c.options.disable_all_suggestions && c.options.disable_all_suggestions.toString(), search_application: c.options.search_application || undefined, search_results_configuration: c.options.search_results_configuration || undefined, searchFilters : $location.search().searchFilters || c.data.aiSearchSourceFilter || undefined, disableSpellCheck: 'false' }; $rootScope.$applyAsync(function() { var navigateToUrl = $location.search(newUrlObj); if (shouldReloadPage) $scope.$emit("sp.page.reload"); spAriaFocusManager.navigateToLink(navigateToUrl.url()); }); } } c.navigate = spAISearchResults.navigate; } function initializeZingSearch() { c.options.glyph = c.options.glyph || 'search'; c.options.title = c.options.title || c.data.searchMsg; c.options.color = c.options.color || "default"; c.searchTerm = c.data.q; c.searchQuery = ""; c.pageID = $scope.page && $scope.page.id; c.showSuggestions = c.data.searchTypeBehavior === "suggestions" && c.data.isSuggestionsEnabled === "true"; c.suggestionsLimit = c.options.limit || ""; c.latitude = null; c.longitude = null; c.isGlideSignalsLoaded = false; c.isLocationTrackerDisabled = c.data.isLocationTrackerDisabled === "true"; c.isTypeAheadEnabled = c.data.isTypeAheadEnabled === "true"; c.sendAnalytics = function(type){ var payload= {}; payload.name = "Initiate Search"; payload.data = {}; payload.data["Keyword"] = (type == 'User Entered' ? c.searchTerm : c.searchQuery); payload.data["Type"] = type; payload.data["Page ID"] = c.pageID; snAnalytics.addEvent(payload); }; if (c.isTypeAheadEnabled) { if (window.GlideSignals) initializeGlideSignals(); else { $rootScope.$on("sp.defer_scripts.loaded", function(){ if (window.GlideSignals) initializeGlideSignals(); }); } if (!c.isLocationTrackerDisabled) { setUserLocationCoords(function(coords) { c.latitude = coords.latitude; c.longitude = coords.longitude; }); } c.searchType = c.data.searchType; // Zing Search functions function initializeGlideSignals() { if (!c.isLocationTrackerDisabled && window.GlideSignals.init) window.GlideSignals.init(); c.isGlideSignalsLoaded = window.GlideSignals.trackEvent || c.isGlideSignalsLoaded; } c.trackSuggestionsRenderedEvent = function(searchQueryLength, responseTimeInMilliSeconds){ if(c.isGlideSignalsLoaded) GlideSignals.trackEvent('SEARCH_SUGGESTIONS_RENDERED', GlideSignals.priority.INFO, {'applicationId': c.data.portalID, 'searchQueryLength': searchQueryLength, 'totalSuggestionsCount': c.totalSuggestionsCount, 'userHistorySuggestionsCount' : c.userHistorySuggestionsCount, 'instanceHistorySuggestionsCount' : c.instanceHistorySuggestionsCount, 'responseTime': responseTimeInMilliSeconds+' ms' }); }; c.trackSearchClickedEvent = function(model){ if (!c.isGlideSignalsLoaded) return; if(c.showSuggestions) { GlideSignals.trackEvent('SEARCH_SUGGESTION_CLICKED', GlideSignals.priority.INFO, {'applicationId': c.data.portalID, 'searchQueryLength' : c.searchQuery.length, 'suggestionClickedLength': model.name.length, 'totalSuggestionsCount': c.totalSuggestionsCount, 'suggestionClickedType': model.type, 'aggregatedClickIndex': getSearchItemIndex(c.searchItems, model), 'userHistorySuggestionsCount' : c.userHistorySuggestionsCount, 'instanceHistorySuggestionsCount' : c.instanceHistorySuggestionsCount, 'suggestionsDisplayLimit': c.suggestionsLimit, 'relativeClickIndex': getRelativeSearchItemIndex(c.searchItems, model) }) } else { GlideSignals.trackEvent('SEARCH_TYPEAHEAD_CLICKED', GlideSignals.priority.INFO, {'applicationId': c.data.portalID, 'searchQueryLength' : c.searchQuery.length, 'typeaheadClickedLength': model.name && model.name.length, 'resultSysId': model.sys_id, 'clickIndex': model.query_location != null ? model.query_location : getSearchItemIndex(c.searchItems, model), 'sourceId': model.type != null ? model.type : model.table, 'typeaheadDisplayLimit': c.options.limit }) } } c.onSelect = function($item, $model, $label) { c.sendAnalytics(c.showSuggestions ? "Suggestions" : "Typeahead"); c.searchTerm = ""; // prevents unexpected result if user quickly clicks search button after selecting if (c.showSuggestions) $item.url = "?id=search&spa=1&q="+encodeURIComponent($item.name); if(!$item.url || $item.url === "") return; if (!c.showSuggestions) { var index = $(".typeahead-popup li.active").data('index'); c.trackSearchResultClicked(index + 1); } c.trackSearchClickedEvent($model); if ($item.target) window.open($item.url, $item.target); else { var newUrl = $location.url($item.url); spAriaFocusManager.navigateToLink(newUrl.url()); } }; function recordSuggestionsCount(){ c.instanceHistorySuggestionsCount = 0; c.userHistorySuggestionsCount = 0; c.searchItems.forEach(function(item){ var isInstanceHistory = item.type === 'INSTANCE_HISTORY'; c.instanceHistorySuggestionsCount += isInstanceHistory; c.userHistorySuggestionsCount += !isInstanceHistory; }); } function getSearchItemIndex(items, targetItem) { return (items || []).findIndex(function(item) { return item.name === targetItem.name; }); } function getRelativeSearchItemIndex(items, targetItem) { var groupedItems = (items || []).filter(function(item) { return item.type === targetItem.type; }); return getSearchItemIndex(groupedItems, targetItem); } c.getSearchSuggestions = function(query) { c.searchQuery = query; if ($location.search().q == c.searchQuery) return; var payload = { params: { "sysparm_term" : c.searchQuery, "sysparm_sp_portal_id": c.data.portalID, "sysparm_suggestions_limit": c.suggestionsLimit, "sysparm_search_sources": c.data.searchSourceSysIds || "" }, headers : {'Accept' : 'application/json'} }; var requestTimeStamp = new Date().getTime(); return $http.get("/api/now/search/sp_suggestions", payload).then(function(response){ var responseTimeStamp = new Date().getTime(); var responseTimeInMilliSeconds = (responseTimeStamp - requestTimeStamp); var result = response.data.result; c.totalSuggestionsCount = result != null ? result.entries.length : 0; if (c.totalSuggestionsCount > 0) sendLiveMessage(c.totalSuggestionsCount); c.searchItems = result.entries.map(function(item) { item.query = getQueryToHighlight(item, c.searchQuery); item.glyph = getIcon(item.type); item.term = item.name; return item; }); recordSuggestionsCount(); c.trackSuggestionsRenderedEvent(query.length, responseTimeInMilliSeconds); return c.searchItems; }); }; function getSearchSources(results, c) { var sources = {}; c.data.searchSources.map(function(key) { sources[key] = 0; }); results.forEach(function(item) { if(sources[item.type]) sources[item.type]++; else sources[item.type] = 1; }); var searchSources = []; Object.keys(sources).map(function(key) { var source_id = c.data.searchSourceConfiguration[key] ? c.data.searchSourceConfiguration[key].sys_id : key; searchSources.push({ source_id: source_id, number_of_results: sources[key] }); }); return searchSources; } function getSearchResultsSignals(results, c) { return results.map(function(item) { return { record_id: item.sys_id, table_name: item.table }; }); } function setUserLocationCoords(cb) { var onSuccess = function(pos) { return cb({ latitude: pos.coords.latitude, longitude: pos.coords.longitude }) }; var onError = function() { return cb({ latitude: null, longitude: null }); }; if (window.navigator.geolocation) { window.navigator.geolocation.getCurrentPosition(onSuccess, onError, { enableHighAccuracy: true }); } } function getResultDescription(result){ return result.name || result.primary || result.sec_title; } c.trackSearchResultClicked = function(rank) { if (!rank || rank < 1) return ; var query = c.latestQuery; var results = _.get(c.data, 'results', []); var result = results[rank-1]; var sourceTable = result.table || null; var payloadObject = { action: "GlideSPSearchAnalyticsUpdateRank", payload: { query: query, portal_id: this.data.portalID, page_id: this.pageID, results_per_source: getSearchSources(results, this), search_results: getSearchResultsSignals(results, this), refinement_occurred: false, signal_type: "CLICK", signal_value: rank, browser_info: $window.navigator.userAgent, location: { latitude: c.latitude, longitude: c.longitude }, result_event_sys_id : result.sys_id, label_description : getResultDescription(result), source_table: sourceTable } }; $window.spSearchAnalytics = { query: query }; c.server.get(payloadObject); } c.getResults = function(query) { c.searchQuery = query; var payload = { "query": c.searchQuery, "portal": c.data.portalID, "page": c.pageID, "source": c.data.searchSources, "include_facets": false, "searchType": "typeahead" }; if (c.options.limit || c.options.limit == 0) payload.count = c.options.limit; return $http.post("/api/now/sp/search?sysparm_cancelable=true", payload).then(function(response) { // Prevents typeahead from displaying suggestions if queries from page and input are the same if ($location.search().q == c.searchQuery) return; var result = response.data.result; var resultCount = result != null ? result.results.length : 0 sendLiveMessage(resultCount); c.data.results = result.results; c.latestQuery = c.searchQuery; c.searchItems = result.results.map(function(item) { var config = c.data.searchSourceConfiguration[item.__search_source_id__]; if (!item.url && config.linkToPage) { item.url = "?id=" + config.linkToPage; if (item.sys_id) item.url += "&sys_id=" + item.sys_id; if (item.table) item.url += "&table=" + item.table } if (item.link) item.url = item.link.indexOf('sys_attachment.do') != -1 ? item.link : config.linkToPage ? item.url : item.link; if (config.type == "ADVANCED") { item.templateID = config.template; } else { item.glyph = config.glyph; } return item; }); if (c.searchItems.length == 0) c.searchItems = [{"primary": c.data.noResultsFoundMsg}]; return c.searchItems; }); } } c.searchSourceChanged = function(newUrl, oldUrl) { var newUrlParams = newUrl.match(regExpr), oldUrlParams = oldUrl.match(regExpr); if(!newUrlParams && !oldUrlParams) { return false; } if((!newUrlParams && oldUrlParams) || (newUrlParams && !oldUrlParams)) { return true; } return newUrlParams[1] !== oldUrlParams[1]; } c.submitSearch = function() { c.sendAnalytics("User Entered"); var shouldReloadPage = c.data.refreshPageOnSearch && $location.search().id === 'search'; if (c.searchTerm) { var newUrl = $location.search({ id: 'search', spa: '1', t: c.searchType, q: c.searchTerm }); if (shouldReloadPage) $scope.$emit("sp.page.reload"); spAriaFocusManager.navigateToLink(newUrl.url()); //Pass the current page ID to search page for Search Analytics $window.spSearchAnalytics = { page_id: c.pageID }; } } function sendLiveMessage(resultCount) { spAriaUtil.sendLiveMessage(resultCount + " " + c.data.resultMsg + " " + (resultCount > 0 ? ' ' + c.data.navigationMsg : '')); } function getIcon(itemType) { return itemType === "INSTANCE_HISTORY" ? 'search' : 'clock-o'; } function getQueryToHighlight(item, query) { return item.type === "INSTANCE_HISTORY" ? item.name.substring(query.length) : query; } } } Server Script: (function() { var portalRecord = $sp.getPortalRecord(); data.aisEnabled = $sp.isAISearchEnabled(); data.q = $sp.getParameter('q'); if (options.refresh_page_on_search_submission == undefined) data.refreshPageOnSearch = true; else data.refreshPageOnSearch = options.refresh_page_on_search_submission; // AI Search if (data.aisEnabled) { data.searchApplicationId = options.search_application || portalRecord.getValue('search_application'); options.placeholder = options.placeholder ? gs.getMessage(options.placeholder) : gs.getMessage('Search'); data.aiSearchSourceFilter = ""; var aiSearchSource = options.ai_search_source_filter; var filterGr = new GlideRecord('sys_search_filter'); data.exactMatchRegex = new GlideSystem().getProperty("com.snc.service_portal.typeahead.exact_match_request_criterion_regex") filterGr.addQuery('ais_search_source', aiSearchSource); filterGr.addQuery('search_context_config', data.searchApplicationId); filterGr.addActiveQuery(); filterGr.orderBy('order'); filterGr.query(); if (filterGr.next()) data.aiSearchSourceFilter = filterGr.getUniqueValue(); var portalId = portalRecord && portalRecord.getUniqueValue(); var resultMap = $sp.getAISearchResultsActionConfig(data.searchApplicationId, portalId); resultMap = JSON.parse(resultMap); data.tableToSourceMap = resultMap.tableToSourceMap; data.sourceToPageMap = resultMap.sourceToPageMap; data.urlMap = resultMap.urlMap; data.portalId = portalId; return; } if (input && input.action === "GlideSPSearchAnalyticsUpdateRank") { input.action = ""; var textSearchAnalytics = $sp.publishSearchAnalytics(JSON.stringify(input.payload)); return ; } if (options.title) { options.title = gs.getMessage(options.title); } data.resultMsg = gs.getMessage("Search results."); data.navigationMsg = gs.getMessage("To navigate, use up and down arrow keys."); data.portalID = portalRecord && portalRecord.getUniqueValue(); data.searchMsg = gs.getMessage("Search"); data.searchSuggestionsMsg = gs.getMessage("suggestions"); data.noResultsFoundMsg = gs.getMessage("No results found"); data.isSuggestionsEnabled = gs.getProperty('glide.search.suggestions.enabled'); data.searchTypeBehavior = gs.getProperty('glide.service_portal.search_as_you_type_behavior').toLowerCase(); data.typeaheadWaitMS = getIntProperty('glide.service_portal.typeahead.wait_ms', '1000'); data.typeaheadMinLength = getIntProperty('glide.service_portal.typeahead.min_length', '3'); data.isLocationTrackerDisabled = gs.getProperty('glide.service_portal.disable_location_tracker'); data.isTypeAheadEnabled = gs.getProperty('glide.service_portal.enable_typeahead', 'true'); var searchSources; data.searchType = null; data.searchSources = []; if ($sp.getParameter("id") == "search" && $sp.getParameter("t")) { data.searchType = $sp.getParameter("t"); searchSources = $sp.getSearchSources(data.portalID); } else { var contextualSearchSourceIDs = options.contextual_search_sources || null; searchSources = $sp.getSearchSources(data.portalID, contextualSearchSourceIDs); if (searchSources.length == 1) { data.searchType = searchSources[0].id; } } data.searchSourceSysIds = []; data.typeaheadTemplates = {}; data.searchSourceConfiguration = {}; searchSources.forEach(function(source) { data.searchSourceSysIds.push(source.sys_id); if (source.isTypeaheadEnabled) { data.searchSources.push(source.id); } var sourceTemplateConfiguration = { sys_id: source.sys_id, glyph: source.typeaheadGlyph, linkToPage: source.typeaheadPage }; if (source.isAdvancedTypeaheadConfig) { sourceTemplateConfiguration.type = "ADVANCED"; sourceTemplateConfiguration.template = "sp-typeahead-" + source.id + ".html"; data.typeaheadTemplates["sp-typeahead-" + source.id + ".html"] = $sp.translateTemplate(source.typeaheadTemplate); } else { sourceTemplateConfiguration.type = "SIMPLE"; if (!sourceTemplateConfiguration.linkToPage) console.log("Warning: No typeahead page or URL provided for search source " + source.name); } data.searchSourceConfiguration[source.id] = sourceTemplateConfiguration; }); function getIntProperty(name, defaultValue) { var answer = parseInt(gs.getProperty(name, defaultValue)); return isNaN(answer) ? defaultValue : answer; } })();