- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎04-25-2021 01:46 AM
Hello All, I am new to SP and Angular. Can you please help me with a widget.
Here on search option i only get Item name, But instead we need Item name + short description
this is my Client code & below that is HTML code, Can you please suggest me what changes are required here to get Item name + short description
Service Portal Search : [ We need Item name + Short description ]
Client Code:
function ($http, $filter, $location,spAriaUtil, $window, $scope, spAriaFocusManager, snAnalytics) {
var c = this;
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 > 0 ? c.options.limit : "";
c.latitude = null;
c.longitude = null;
c.isGlideSignalsLoaded = false;
c.isLocationTrackerDisabled = c.data.isLocationTrackerDisabled === "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);
};
function initializeGlideSignals() {
if (!c.isLocationTrackerDisabled && window.GlideSignals.init) {
window.GlideSignals.init();
}
if (window.GlideSignals.trackEvent) {
c.isGlideSignalsLoaded = true;
}
}
if (window.GlideSignals)
initializeGlideSignals();
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) {
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&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){
return item.type === 'INSTANCE_HISTORY' ? c.instanceHistorySuggestionsCount++ : c.userHistorySuggestionsCount++;
});
}
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;
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.map(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) {
var recordId = item.sys_id;
var tableName = item.table;
return {
record_id: recordId,
table_name: tableName
};
});
}
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
});
};
return window.navigator.geolocation.getCurrentPosition(onSuccess, onError, {
enableHighAccuracy: true
});
}
if(!c.isLocationTrackerDisabled) {
setUserLocationCoords(function(coords) {
c.latitude = coords.latitude;
c.longitude = coords.longitude;
});
}
function getResultDescription(result){
var description = null;
if(result.name != null)
description = result.name;
else if(result.primary != null)
description = result.primary;
else if(result.sec_title != null)
description = result.sec_title;
return description;
}
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 ? 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,
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,
signal_type: "CLICK",
signal_value: rank
}
};
$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", 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 (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.searchType = c.data.searchType;
$scope.$on('$locationChangeSuccess', onLocationChangeSuccess);
function onLocationChangeSuccess(event, newUrl, oldUrl) {
if(searchSourceChanged(newUrl, oldUrl)) {
var newUrlParams = newUrl.match(/t=.+/);
if(!newUrlParams) {
c.searchType = null;
} else {
c.searchType = newUrlParams[0].split("&")[0].substring(2);
}
}
}
function searchSourceChanged(newUrl, oldUrl) {
var newUrlParams = newUrl.match(/t=.+/),
oldUrlParams = oldUrl.match(/t=.+/);
if(!newUrlParams && !oldUrlParams) {
return false;
}
if((!newUrlParams && oldUrlParams) || (newUrlParams && !oldUrlParams)) {
return true;
}
return newUrlParams[0].split("&")[0] !== oldUrlParams[0].split("&")[0];
}
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;
}
}
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎04-25-2021 06:33 AM
I made changes with OOTB. What I did is update the typeahead template of Catalogs Search Source of /sp Service Portal
Code:
<!-- prefer item picture to item icon, prefer item icon to default icon -->
<div onclick="window.GlideWebAnalytics.trackEvent('Service Catalog', 'Catalog Search Type Ahead', 'Item Clicked')">
<i ng-if="match.model.picture" class="ta-img" style="background-image:url('{{match.model.picture}}?t=small')"></i>
<i ng-if="!match.model.picture && match.model.icon" class="ta-icon" style="background-image:url('{{match.model.icon}}'); width:16px; height:16px"></i>
<i ng-if="!match.model.picture && !match.model.icon" class="ta-icon fa fa-{{match.model.default_icon}}"></i>
<span ng-bind-html="match.label | uibTypeaheadHighlight:query"></span>
<p>
<span ng-bind-html="match.model.short_description | uibTypeaheadHighlight:query"></span>
</p>
<strong ng-if="match.model.type == 'sc_content' && match.model.content_type == 'external'">âžš</strong>
</div>
Result:
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎04-25-2021 06:04 AM
I suppose that you try to make changes on the wrong place. The code, which you posted seems a copy of typeahead-search widget. Typeahead search widget only display HTML fragments generated by so named Search Sources. The HTML fragments will be inserted inside of sp-typeahead.html template and displayed as dropdown. So the correct place, where you should make the modification will be either typeahead template of the corresponding Search Source (see sp_search_source Table) or the sp-typeahead.html template, which you can see in Related List of typeahead-search widget.
You can modify sp-typeahead.html template and insert <span>{{match.model}}</span> for debugging purpose. You will see JSON representation of the data returned from search source. If the source, where you need insert short description returns short_description, then you can try to insert <span>{{match.model.fields.short_description.display_value}}</span>, which could be the text, which you want to display. The final result you should consider to cut long short description to prevent displaying very long texts in the typehead.
If <span>{{match.model}}</span> doesn't contain short description, then you will have to modify typeahead part of the corresponding Search Source, check "Advanced typeahead config" and to write or modify the typeahead template to include short description. You can start here with <span>{{match.model}}</span> to see raw data and then write HTML fragment corresponding your requirements.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎04-25-2021 07:48 AM
Thank You replying me, I'll check this
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎04-25-2021 08:01 AM
This is the code written inmy HTML line
<div id="homepage-search" class="hidden-xs wrapper-xl">
<div class="wrapper-xl">
<h2 class="text-center text-4x m-b-lg sp-tagline-color" ng-bind="options.title"></h2>
<div ng-if="options.short_description" class="text-center h4 m-b-lg sp-tagline-color" ng-bind="options.short_description"></div>
<sp-widget widget="data.typeAheadSearch" />
</div>
</div>
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎04-25-2021 08:04 AM
<form ng-submit="c.submitSearch()">
<input type="hidden" name="id" value="search"/>
<input type="hidden" name="t" value="{{c.searchType}}"/>
<div class="input-group input-group-{{::c.options.size}}">
<!-- uses ui.bootstrap.typeahead -->
<input ng-if="c.showSuggestions"
name="q" type="text" placeholder="{{::c.options.title}}" ng-model="c.searchTerm"
autocomplete="off"
uib-typeahead="item as item.term for item in c.getSearchSuggestions($viewValue)"
typeahead-wait-ms="c.data.typeaheadWaitMS"
typeahead-min-length="c.data.typeaheadMinLength"
typeahead-focus-first="false"
typeahead-on-select="c.onSelect($item, $model, $label)"
typeahead-template-url="sp-typeahead.html"
typeahead-popup-template-url="sp-typeahead-popup.html"
class="form-control input-typeahead"
role="textbox"
aria-label="{{::c.options.title}}" tabindex="0" aria-haspopup="true">
<input ng-if="!c.showSuggestions"
name="q" type="text" placeholder="{{::c.options.title}}" ng-model="c.searchTerm"
autocomplete="off"
uib-typeahead="item as item.primary for item in c.getResults($viewValue)"
typeahead-wait-ms="c.data.typeaheadWaitMS"
typeahead-min-length="c.data.typeaheadMinLength"
typeahead-focus-first="false"
typeahead-on-select="c.onSelect($item, $model, $label)"
typeahead-template-url="sp-typeahead.html"
typeahead-popup-template-url="sp-typeahead-popup.html"
class="form-control input-typeahead"
role="textbox"
aria-label="{{::c.options.title}}" tabindex="0" aria-haspopup="true">
<span class="input-group-btn">
<button name="search" type="submit" class="btn btn-{{::c.options.color}}"
title="{{::c.data.searchMsg}}" aria-label="{{::c.data.searchMsg}}">
<i ng-if="::c.options.glyph" class="fa fa-{{::c.options.glyph}}"></i>
</button>
</span>
</div>
</form>