Need to modify href in Angular-ng-template to open kb article in new tab
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-08-2022 01:34 AM
Hi,
I want to modify the Angular-ng-template in a widget where it shows kb articles in contextual search. The href from angular-ng-template is like this href="#". I added a target="_blank" beside that and tested. It opens the same page in a new tab but not the kb article. Now i want to open the kb article with its content. as href="#" will point to the same page, i want to modify it to open article in a new tab.
Thanks!

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-08-2022 04:26 AM
Hi,
If you are using href="#" then the redirection is happening at script.
Share the complete HTML tag and client controller script if redirection happens at client controller
Thank you,
Palani
Palani
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-08-2022 07:40 AM
Hi
It is for the Contextual search- Inline results widget on portal. I want to open the kb article clciked to be opened in a new page.
HTML CODE (Angular-ng_template):
<!-- Knowledge Articles -->
<div class="cxs-result-container" id="{{result.id}}">
<div class="row col-md-12">
<div class="cxs-result-icon col-md-1">
<i class="fa fa-file-text" aria-hidden="true"></i>
</div>
<div class="col-md-11">
<div class="cxs-result-title">
<a ng-attr-id="{{cxs.RESULT_TITLE_ID}}{{$index}}" ng-click="displayDetail($index)" href="#" ><span class="cxs-result-icon"><i class="fa fa-file-text" aria-hidden="true"></i></span><span>{{result.title}}</span></a>
<span ng-if="result.meta.source == 'pinned'" class="cxs-lozenge">${Pinned}</span>
<span ng-if="isRelevant(result)" class="cxs-lozenge">{{thisHelpedActionDetails(result).actionLabel}}</span>
</div>
<div class="cxs-meta-row" ng-if="cxs.property.show_meta_data">
<span class="cxs-result-breadcrumbs">
<span>{{result.meta.knowledgeBase}}</span>
<span role="separator" ng-if="result.meta.parentCategories">|</span>
<span ng-if="result.meta.parentCategories">{{getKBParentCategories(result)}}</span>
</span>
</div>
<div class="cxs-result-snippet">
<span ng-bind-html="result.snippet + '…'"></span>
</div>
<div class="cxs-meta-row" ng-if="cxs.property.show_meta_data">
<ul class="cxs-article-info">
<li ng-if="cxs.kb_property.show_author">
<span class="cxs-article-label">${Author}:</span>
<span class="cxs-article-value">{{result.meta.author}}</span>
</li>
<li ng-if="cxs.kb_property.show_view_count">
<span ng-if='result.meta.viewCount != 1' class="cxs-article-label">{{i18n.format(c.data.i18nMsgs.views, result.meta.viewCount)}}</span>
<span ng-if='result.meta.viewCount == 1' class="cxs-article-label">{{i18n.format(c.data.i18nMsgs.view, result.meta.viewCount)}}</span>
<span class="cxs-article-value"></span>
</li>
<li ng-if="cxs.kb_property.show_published">
<span class="cxs-article-label">${Updated}:</span>
<span class="cxs-article-value">{{result.meta.published}}</span>
</li>
<li ng-if="cxs.kb_property.show_rating">
<span role="presentation" class = 'sr-only'>{{getRatingDesc(result.meta.rating)}}</span>
<span class="cxs-rating-readonly" aria-hidden='true'>
<i ng-repeat="i in [1,2,3,4,5]" ng-class="(getRating(result.meta.rating)>=i) ? ['icon-star', 'cxs-icon-star'] :['icon-star-empty', 'cxs-icon-star-empty']" />
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
Client Controller :
function($scope, $rootScope, $timeout, $http, modelUtil, contextualSearch, contextualFeedback, $log, i18n, spUtil, $sce) {
var c = this;
if (c.data.useAISA)
$('#contextual_search_results').css('display', 'none');
var ARIA_MSG_GAP = 1000;
var PREVIEW_STR = "Preview";
$scope.i18n = i18n;
c.data.cxs.RESULT_TITLE_ID = 'result_title_';
var ariaMsgs = c.data.ariaMsgs;
$scope.cxs = c.data.cxs;
var cxs = $scope.cxs; // local pointer to simplify code
if (cxs.config && cxs.config.search_variable) {
cxs.trigger = {
field: $scope.page.g_form.getField("IO:" + cxs.config.search_variable.value)
};
}
/**
* Everytime a request is submitted, the search text stored as previousSearchTerm
* @type {string}
*/
cxs.previousSearchTerm = null;
cxs.delayedSearch = {
/**
* searchResponsePending: set to true everytime a request is submitted, and false when response is received
* @type {boolean}
*/
searchResponsePending: false,
/**
* Stores the delayed search term when search response is pending.
* And will be used to trigger a search when response returns
* @type {string}
*/
delayedSearchTerm: null
}
cxs.display = {
collapsed: false
};
function clearDisplay() {
delete(cxs.display.results);
delete(cxs.display.result_detail);
delete(cxs.display.result_index);
delete(cxs.display.result);
}
$scope.getRatingDesc = function(rating) {
rating = $scope.getRating(rating);
if(rating == 0)
return c.data.i18nMsgs.noRating;
return i18n.format(c.data.i18nMsgs.rating, $scope.getRating(rating));
}
$scope.getRating = function(rating) {
return Math.round(rating || 0);
}
function hasResults(){
return cxs.display.results && cxs.display.results.length != 0;
}
/**
* Construct and submit a search request
* @param {string} newValue the search term
*/
function startSearch(newValue) {
if (cxs.trigger.timeout)
$timeout.cancel(cxs.trigger.timeout);
if (newValue === cxs.previousSearchTerm)
return;
$scope.setAriaStatus(i18n.format(ariaMsgs.searching, newValue), 0);
var searchRequest = contextualSearch.newSearchRequest({
context: cxs.config.cxs_context_config.value,
query: {
freetext: newValue,
/*
Passing the knowledge base list with the request as search parameter,
so that knowledge articles will be filtered based on this list.
Also passing the catalog list as search parameter to filter catalog items.
*/
searchParameters: {
knowledgeBase: cxs.knowledgeBase,
catalog: cxs.catalog
}
},
meta: {
limit: cxs.config.limit.value,
window: {
start: 0,
end: cxs.config.results_per_page.value
},
session: cxs.session,
includePinnedArticles: true
},
g_form: $scope.page.g_form
});
// Submit search request and handle results returned from promise
searchRequest.submit().then(
function(response) {
clearDisplay();
cxs.response = response;
cxs.display.results = response.results;
cxs.tsQueryId = response.meta.tsQueryId;
if(hasResults()) {
var resultCount = response.results.length || '';
if($scope.hasMoreResults())
$scope.setAriaStatus(i18n.format(ariaMsgs.searchCompleted, resultCount), ARIA_MSG_GAP);
else
$scope.setAriaStatus(i18n.format(ariaMsgs.allResultsLoaded, resultCount), ARIA_MSG_GAP);
}
else
$scope.setAriaStatus(i18n.format(ariaMsgs.noMatchingResults, newValue), ARIA_MSG_GAP);
if (cxs.delayedSearch.searchResponsePending) {
cxs.delayedSearch.searchResponsePending = false;
if (cxs.delayedSearch.delayedSearchTerm !== null && cxs.delayedSearch.delayedSearchTerm !== cxs.previousSearchTerm)
searchUponTriggerValueChange(cxs.delayedSearch.delayedSearchTerm);
cxs.delayedSearch.delayedSearchTerm = null;
}
},
function(response) {
clearDisplay();
delete(cxs.response);
}
);
cxs.previousSearchTerm = newValue;
cxs.delayedSearch.searchResponsePending = true;
}
/**
* Event handler for field value change, and attempts to trigger a debounced search
* @param {string} newValue
* @param {string} oldValue
*/
function searchUponTriggerValueChange(newValue, oldValue) {
preSearchValidation(newValue, false);
}
/**
* Triggers a immediate/debounced search
* @param {string} newValue
* @param {boolean} forceImmediateSearch
*/
function preSearchValidation(newValue, forceImmediateSearch) {
if (cxs.trigger.timeout)
$timeout.cancel(cxs.trigger.timeout);
var trimmedNewValue = newValue.trim();
var charLen = newValue.replace(/\s/g, '').length;
if (!trimmedNewValue || charLen < cxs.property.min_length) {
clearDisplay();
delete(cxs.response);
$scope.setAriaStatus(ariaMsgs.noResultsToDisplay, 0);
cxs.previousSearchTerm = trimmedNewValue;
return;
}
if (forceImmediateSearch){
startSearch(trimmedNewValue);
return;
}
if (cxs.delayedSearch.searchResponsePending) {
cxs.delayedSearch.delayedSearchTerm = trimmedNewValue;
return;
}
if (cxs.property.wait_time >= 0)
cxs.trigger.timeout = $timeout(startSearch, cxs.property.wait_time, true, trimmedNewValue);
}
if (!c.data.useAISA && cxs.trigger) {
$scope.$watch("cxs.trigger.field.stagedValue", searchUponTriggerValueChange);
var el = document.getElementById('sp_formfield_' + cxs.trigger.field.name);
if (el)
el.addEventListener('blur', function(event) {
preSearchValidation(cxs.trigger.field.stagedValue, true);
});
}
$scope.getMoreResults = function() {
if (!$scope.hasMoreResults())
return;
// Set limit if request_next exceeds it.
if (cxs.response.request_next.meta.window.end > cxs.config.limit.value)
cxs.response.request_next.meta.window.end = cxs.config.limit.value;
$scope.setAriaStatus(ariaMsgs.loadingMoreResults, 0);
cxs.response.request_next.submit().then(
function(response) {
cxs.response = response;
cxs.display.results = cxs.display.results.concat(response.results);
if($scope.hasMoreResults())
$scope.setAriaStatus(ariaMsgs.resultsLoaded, ARIA_MSG_GAP);
else
$scope.setAriaStatus(ariaMsgs.allResultsLoaded, ARIA_MSG_GAP);
},
function(response) {
$log.info("BAD");
}
);
};
$scope.hasMoreResults = function() {
return cxs.response.meta.has_more_results
&& cxs.response.request_next.meta.window.start < cxs.config.limit.value;
};
$scope.displayDetail = function(resultIndex) {
if (!cxs.display.results[resultIndex])
return;
var result = cxs.display.results[resultIndex];
if (result._record && !result.disable_cache) {
cxs.display.result_index = resultIndex;
cxs.display.result_detail = result._record;
cxs.display.result = result;
$scope.sendFeedback(PREVIEW_STR,cxs.display.result);
return;
}
// Lookup if we haven't already.
var id = result.id.split(":");
if(result.meta.source === "community_blog") {
spUtil.get("community-content-blog",{sys_id: id[1], "type" : "cxs_view", "read_only" : true, "frameless" : true}).then(function(widgetResponse) {
result._record = widgetResponse;
result.disable_cache = true;
cxs.display.result_index = resultIndex;
cxs.display.result_detail = result._record;
cxs.display.result = result;
cxs.display.result.widget_records = [];
cxs.display.result.widget_records.push(result._record);
$scope.sendFeedback(PREVIEW_STR,cxs.display.result);
});
}
else if(result.meta.source === "community_question" || result.meta.source === "community_answer") {
spUtil.get("community-content-question",{sys_id: id[1], "type" : "cxs_view", "read_only" : true, "frameless" : true}).then(function(widgetResponse) {
result._record = widgetResponse;
result.disable_cache = true;
cxs.display.result_index = resultIndex;
cxs.display.result_detail = result._record;
cxs.display.result = result;
cxs.display.result.widget_records = [];
cxs.display.result.widget_records.push(result._record);
$scope.sendFeedback(PREVIEW_STR,cxs.display.result);
});
}
else if(result.meta.source === 'catalog'){
$http.get("/api/sn_sc/servicecatalog/items/" + id[1]).then(
function(response) {
result._record = response.data.result;
cxs.display.result_index = resultIndex;
cxs.display.result_detail = result._record;
cxs.display.result = result;
$scope.sendFeedback(PREVIEW_STR,cxs.display.result);
}
);
}
else {
$http.get("/api/now/table/" + id[0] + "?sysparm_display_value=all&sysparm_query=sys_id%3D" + id[1]).then(
function(response) {
result._record = response.data.result[0];
if(result.meta.source == 'knowledge' && result._record.text){
c.server.get({'action': "get_article_content","articleId":id[1]}).then(function(resp){
result._record.text.display_value = resp.data.knowledge_content;
});
}
cxs.display.result_index = resultIndex;
cxs.display.result_detail = result._record;
cxs.display.result = result;
$scope.sendFeedback(PREVIEW_STR,cxs.display.result);
},
function(response) {
$log.info("BAD II");
}
);
}
};
$scope.toggleExpandCollapse = function() {
cxs.display.collapsed = !cxs.display.collapsed;
};
$scope.getResultTemplate = function(result) {
return (result && result.meta.source) ? "cxs-result-" + result.meta.source.toLowerCase() : "cxs-result-default";
};
$scope.getDetailTemplate = function() {
var result = cxs.display.results[cxs.display.result_index];
return (result && result.meta.source) ? "cxs-detail-" + result.meta.source.toLowerCase() : "cxs-detail-default" ;
};
$scope.getKBParentCategories = function(result) {
if (!result)
return;
var parentCategories = [];
// copy by value. slice() does not work on this array
if (result.meta.parentCategories)
for (var i = 0; i < result.meta.parentCategories.length; i++)
parentCategories.push(result.meta.parentCategories[i]);
return parentCategories.reverse().join(' > ');
};
// Detail navigation
$scope.backToResults = function() {
delete(cxs.display.result_detail);
delete(cxs.display.result_index);
delete(cxs.display.result);
$scope.onBackToResult();
};
$scope.toNext = function() {
if (cxs.display.result_index >= cxs.display.results.length-1 && $scope.hasMoreResults())
$scope.getMoreResults();
if ($scope.hasNext())
$scope.displayDetail(cxs.display.result_index + 1);
};
$scope.toPrev = function() {
if ($scope.hasPrev())
$scope.displayDetail(cxs.display.result_index - 1);
};
$scope.hasNext = function() {
return cxs.display.result_index < cxs.display.results.length-1 || $scope.hasMoreResults();
};
$scope.hasPrev = function() {
return cxs.display.result_index > 0;
};
$scope.sendFeedback = function(actionValue, result) {
if (!result)
result = cxs.display.results[cxs.display.result_index];
var relevance = true;
var thisHelpedActionDetails = $scope.thisHelpedActionDetails(result);
if(thisHelpedActionDetails && thisHelpedActionDetails.actionValue === actionValue)
relevance = !$scope.isRelevant(result);
var feedbackRequest = contextualFeedback.newFeedbackRequest({
session: cxs.session,
search_request: cxs.response.request,
relevant_doc: result.id,
relevant_doc_url: result.sp_link || result.link,
relevance: actionValue,
relevant: relevance,
score: result.meta.score,
index: cxs.display.result_index,
displayed_on: cxs.displayed_on
});
feedbackRequest.submit().then(
function(response) {
if (!result.meta.relevance)
result.meta.relevance = {};
if(thisHelpedActionDetails && thisHelpedActionDetails.actionValue !== actionValue)
result.meta.relevance[actionValue] = true;
else {
var thisHelpedAction = $scope.thisHelpedActionDetails(cxs.display.result);
// if helped action inactive it will not come in the result
if(thisHelpedAction != null && thisHelpedAction != '') {
if(actionValue == thisHelpedAction.actionValue)
result.meta.relevance[actionValue] = !$scope.isRelevant(result);
}
}
},
function(response) {
$log.info("BAD III");
}
);
if(result.meta.source && (result.meta.source.toLowerCase() == 'knowledge' || result.meta.source.toLowerCase() == 'pinned')) {
var id = result.id.split(":");
c.server.get({'action': "register_kb_view", "articleId":id[1], "tsQueryId": cxs.tsQueryId}).then(function(resp){
if (result._record.sys_view_count && resp.data.viewCount) {
result._record.sys_view_count.display_value = resp.data.viewCount;
result._record.sys_view_count.value = resp.data.viewCount;
}
});
}
};
$scope.thisHelpedActionDetails = function(result) {
if (!result)
result = cxs.display.results[cxs.display.result_index];
return $scope.parseJson(result.searchResultActions.this_helped);
};
$scope.orderActionDetails = function(result) {
if (!result)
result = cxs.display.results[cxs.display.result_index];
return $scope.parseJson(result.searchResultActions.order);
};
$scope.attachActionDetails = function(result) {
if (!result)
result = cxs.display.results[cxs.display.result_index];
return $scope.parseJson(result.searchResultActions.attach);
};
$scope.cxsTrust = function(html) {
return $sce.trustAsHtml(html);
};
$scope.isRelevant = function(result) {
if (!result)
result = cxs.display.results[cxs.display.result_index];
var thisHelpedActionDetails = $scope.thisHelpedActionDetails(result);
var relevancy = result.meta.relevance && thisHelpedActionDetails && result.meta.relevance[thisHelpedActionDetails.actionValue];
if(relevancy)
return relevancy;
return false;
};
$scope.parseJson = function(json) {
var parsedJSON = '';
if(json)
parsedJSON = JSON.parse(json);
return parsedJSON;
};
var deregister = $rootScope.$on("$sp.sc_cat_item.submitted", function(event, response){
contextualFeedback.link(cxs.session, response.table, response.sys_id);
});
$scope.$on('$destroy', function(){
deregister();
});
}
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
‎09-08-2022 10:20 PM
Hi
Were you able to find anything from the script? Thanks in advance