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;
}
})();