Adding favorite functionality in portal for sc_category page ,While clicking the star box adding the item in favorite and start icon is filling as well.This is working popular item widget only .while going individually category item star icon is e

praveen50
Tera Contributor

Adding favorite functionality in portal for sc_category page ,While clicking the star box adding the item in favorite  and start icon is filling as well.This is working popular item widget  only .while going individually category  item star icon is empty but adding in favourite.How can i resolve this?

 

I have referred this link:Service Catalog Favorites on the Service Portal - ESM Alliance - Global ServiceNow Developers

The  above mentioned same way implemented the but facing the this issue-while going individually category  item star icon is empty but adding in favourite. How can i resolve this?

 

PFB screenshot reference.

find_real_file.png

 

1 REPLY 1

praveen50
Tera Contributor

 

Below widget i cloned from  OOB made few changes highlighted color

widget:sc categry page 

HTML Script:

<div class="m-t-sm " ng-class="{'hidden-xs' : hideItemWidget, 'm-l-sm': !isMobile}">
<h4 ng-if="data.error">{{data.error}}</h4>
<div ng-init="spSearch.targetCatalog()" role="tabpanel" aria-labelledby="{{::data.category_id && data.category_id != -1 ? 'tab_' + data.category_id : null}}" id="tabpanel_{{::data.category_id}}">
<div class="row">
<div class="col-xs-9">
<a ng-click="showCategories()" class="visible-xs m-b-sm pointer" aria-label="${All Categories}">
<i class="fa fa-chevron-left m-r-xs"></i> ${All Categories}
</a>
<h2 class="h4 m-t-none break-word" aria-label="{{::data.category.title}} ${Category}" role="heading" aria-level="2">{{::data.category.title}}</h2>
<p class="hidden-xs break-word">
{{::data.category.description}}
</p>
</div>
<div class="col-xs-3" ng-if="!isMobile">
<div role="tablist" class="pull-right padder-t-sm text-lg toggle" ng-show="!data.error && data.items.length > 0">
<i class="fa fa-th" ng-click="changeView('card')" aria-label="${Card View}" ng-class="{'active' : view == 'card'}" role="tab" aria-selected="{{view == 'card'}}" uib-tooltip="${Card View}" tooltip-placement="top" tooltip-enable="!isTouchDevice()" tooltip-append-to-body="true" aria-label="${Card View}" tabindex="0"></i>
<span class="m-l-sm m-r-sm " aria-hidden="true"> | </span>
<i class="fa fa-list-ul" ng-click="changeView('grid')" aria-label="${Grid View}" ng-class="{'active' : view == 'grid'}" role="tab" aria-selected="{{view == 'grid'}}" uib-tooltip="${Grid View}" tooltip-placement="top" tooltip-enable="!isTouchDevice()" tooltip-append-to-body="true" aria-label="${Grid View}" tabindex="0"></i>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6 col-md-4" ng-if="!data.items.length && !data.error">
${No items in category}
</div>
<table class="table table-striped item-table" ng-if="view == 'grid' && data.items.length > 0" aria-label="{{::data.category.title}}" aria-describedby="id-caption-category">
<caption id="id-caption-category"><span class="sr-only">{{::data.category.title}}</span></caption>
<thead>
<tr>
<th id="id-header-item" scope="col">${Item}</th>
<th id="id-header-description" scope="col">${Description}</th>
<th id="id-header-price" scope="col" ng-if="data.showPrices">${Price}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data.items | orderBy: 'order' | limitTo: data.limit track by item.sys_id" ng-init="startItemList()">
<th headers="id-header-item id-item-{{item.sys_id}}" scope="row" class="th-row-data">
<a sn-focus="{{::item.highlight}}" href="javascript:void(0);" ng-click="onClick($event, item)">
<div>
<img ng-src="{{::item.picture}}?t=small" ng-if="item.picture" alt="" class="m-r-sm m-b-sm item-image pull-left"/>
<span>{{::item.name}}</span>
<span ng-if="item.content_type == 'external'"><span class="sr-only">${External Link}</span> ➚</span>
</div>
</a>
</th>
<td headers="id-header-description id-item-{{item.sys_id}}" class="break-word">{{::item.short_description}}</td>
<td headers="id-header-price id-item-{{item.sys_id}}">{{::item.price}}</td>
</tr>
</tbody>
</table>
<div ng-if="view == 'card'">
<div class="col-sm-6 col-md-4" ng-repeat="item in data.items | orderBy: 'order' | limitTo: data.limit track by item.sys_id" ng-init="startItemList()">
<div class="panel panel-{{::options.color}} b">
<a href="javascript:void(0);" ng-click="onClick($event, item)" class="panel-body block" sn-focus="{{::item.highlight}}">
<div class="overflow-100">
<h3 class="h4 m-t-none m-b-xs text-overflow-ellipsis" title="{{::item.name}}" style="padding-bottom:1px">{{::item.name}}<span ng-if="item.content_type == 'external'" aria-label="${External Link}"> ➚</span></h3>
<img ng-src="{{::item.picture}}?t=small" ng-if="item.picture" alt="{{::item.name}}" class="m-r-sm m-b-sm item-image pull-left" />
<div class="text-muted item-short-desc break-word" aria-hidden="{{::item.name !== null}}" role="presentation">{{::item.short_description}}</div>
</div>
</a>
<span class="sr-only">{{::item.short_description}}</span>
<div class="panel-footer">
<a href="javascript:void(0)" data-toggle="tooltip" data-placement="right" title="Add to favorites">
<div class="pull-right favicon glyphicon glyphicon-heart" ng-class="{'glyphicon-star': item.favorite,'glyphicon-heart-empty':!item.favorite}" ng-click="toggleFavorite(item)">
</div>
</a>
<script>$(document).ready(function(){$('[data-toggle="tooltip"]').tooltip();});</script>
<a aria-label="${View Details} {{::item.name}}" href="javascript:void(0);" ng-click="onClick($event, item)" class="pull-left text-muted" tabindex="-1">${View Details}</a>
<span ng-if="data.showPrices && item.hasPrice" class="pull-right item-price font-bold">{{::item.price}} </span> &nbsp;
</div>
</div>
</div>
</div>
</div>
<div class="text-a-c" ng-if="!stopLoader && data.items.length > 0 && !data.error">
<i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i>
<span class="sr-only">${Loading...}</span>
</div>
<div ng-if="data.show_more && show_popular_item">
<div class="text-a-c">
{{data.more_msg}}
</div>
<button class="m-t-xs btn btn-default btn-loadmore" ng-click="loadMore()">
${Show More}
</button>
</div>
</div>
</div>
<now-message key="Catalogs" value="${Catalogs}"/>

 

CSS:

 

.toggle {
color: $gray-darker;
i:not(.active):hover {
color: $list-group-link-heading-color;
}
}

.fa {
border: 1px solid transparent;
}

.fa.active {
color: $primary;
}


.item-table {
background-color: #fff;
tr {
th {
padding: 16px 10px;
}
td {
padding: 16px 10px;
border: none;
}
}
}

.btn-loadmore {
margin-left: auto;
margin-right: auto;
display: block;
margin-bottom: 20px;
}

.th-row-data {
border-top: none !important;
font-weight: normal !important;
a {
color: $brand-primary-darker !important;
}
}
.favicon {
padding-right: 5px;
margin-top: 2px;
}

 

server script:

(function() {

var arrayUtil = new ArrayUtil();
var currentUserId = gs.getUserID();
var userPrefName = "<customer/project prefix>.sp.favcatitems";
data.favCatItems = [];

var userPrefGR = new GlideRecord("sys_user_preference");
userPrefGR.addQuery("user", currentUserId);
userPrefGR.addQuery("name", userPrefName);
userPrefGR.addNotNullQuery("value");
userPrefGR.setLimit(1);
userPrefGR.query();
if (userPrefGR.next()) {
data.favCatItems = userPrefGR.getValue("value").split(",");

if (input && input.action == "toggleFavorite") {
if (input.addFav) {
//data.favCatItems = arrayUtil.unique(data.favCatItems);
data.favCatItems.push(input.sys_id);
} else {
var remArray = [input.sys_id];
data.favCatItems = arrayUtil.unique(data.favCatItems);
data.favCatItems = arrayUtil.diff(data.favCatItems, remArray);
}
if (data.favCatItems.length == 0) {
userPrefGR.deleteRecord();
} else {
userPrefGR.user = currentUserId;
userPrefGR.name = userPrefName;
userPrefGR.value = data.favCatItems.join(",");
userPrefGR.update();
}
}
}
else{

userPrefGR.user = currentUserId;
userPrefGR.name = userPrefName;
userPrefGR.value = input.sys_id;
userPrefGR.insert();
if (input && input.action == "toggleFavorite") {
if (input.addFav) {
data.favCatItems.push(input.sys_id);
gs.info('Test1: '+data.favCatItems);
}

}}

if (input && input.category_id)
data.category_id = input.category_id;
else
data.category_id = $sp.getParameter("sys_id");

data.catalog_id = $sp.getParameter("catalog_id") ? $sp.getParameter("catalog_id") + "" : "-1";
var catalogsInPortal = ($sp.getCatalogs().value + "").split(",");
var isCatalogAccessibleViaPortal = data.catalog_id == -1 ? true : false;
catalogsInPortal.forEach(function(catalogSysId) {
if (data.catalog_id == catalogSysId) {
isCatalogAccessibleViaPortal = true;
}
})
if (!isCatalogAccessibleViaPortal) {
data.error = gs.getMessage("You do not have permission to see this catalog");
return;
}
var catalogDisplayValue;
if (data.catalog_id && data.catalog_id !== "-1") {
var catalogObj = new sn_sc.Catalog(data.catalog_id);
if (catalogObj) {
if (!catalogObj.canView()) {
data.error = gs.getMessage("You do not have permission to see this catalog");
return;
}
catalogDisplayValue = catalogObj.getTitle();
}
}
if (options && options.sys_id)
data.category_id = options.sys_id;
data.showPrices = $sp.showCatalogPrices();
data.sc_catalog_page = $sp.getDisplayValue("sc_catalog_page") || "sc_home";
data.sc_category_page = $sp.getDisplayValue("sc_category_page") || "sc_category";
catalogDisplayValue = catalogDisplayValue ? catalogDisplayValue : $sp.getCatalogs().displayValue + "";
var catalogIDs = (data.catalog_id && data.catalog_id !== "-1") ? data.catalog_id : $sp.getCatalogs().value + "";
var catalogArr = catalogDisplayValue.split(",");
var catalogIDArr = catalogIDs.split(",");
data.sc_catalog = catalogArr.length > 1 ? "" : catalogArr[0];

data.show_more = false;
if (GlideStringUtil.nil(data.category_id)) {
data.items = getPopularItems();
data.show_popular_item = true;
data.all_catalog_msg = (($sp.getCatalogs().value + "").split(",")).length > 1 ? gs.getMessage("All Catalogs") : "";
data.all_cat_msg = gs.getMessage("All Categories");
data.category = {
title: gs.getMessage("Popular Items"),
description: ''
};
return;
}

data.show_popular_item = false;
// Does user have permission to see this category?
var categoryId = '' + data.category_id;
var categoryJS = new sn_sc.CatCategory(categoryId);
if (!categoryJS.canView()) {
data.error = gs.getMessage("You do not have permission to see this category");
return;
}
data.category = {
title: categoryJS.getTitle(),
description: categoryJS.getDescription()
};

var catalog = $sp.getCatalogs().value;

data.items = [];
var itemsInPage = options.limit_item || 9;

data.limit = itemsInPage;
if (input && input.new_limit)
data.limit = input.new_limit;
if (input && input.items) {
data.items = input.items.slice(); //Copy the input array
}

if (input && input.startWindow) {
data.endWindow = input.endWindow;
} else {
data.startWindow = 0;
data.endWindow = 0;
}

while (data.items.length < data.limit + 1) {
data.startWindow = data.endWindow;
data.endWindow = data.endWindow + itemsInPage;
var itemGR = queryItems(catalog, categoryId, data.startWindow, data.endWindow);
if (!itemGR.hasNext())
break;
fetchItemDetails(itemGR, data.items);
}

if (data.items.length > data.limit)
data.show_more = true;

data.more_msg = gs.getMessage(" Showing {0} items", data.limit);

data.categories = [];
while (categoryJS && categoryJS.getParent()) {
var parentId = categoryJS.getParent();
categoryJS = new sn_sc.CatCategory(parentId);
var category = {
label: categoryJS.getTitle(),
url: '?id=' + data.sc_category_page + '&sys_id=' + parentId
};
data.categories.unshift(category);
}

data.all_catalog_msg = (($sp.getCatalogs().value + "").split(",")).length > 1 ? gs.getMessage("All Catalogs") : "";

function fetchItemDetails(itemRecord, items) {
while (itemRecord.next()) {
var catalogItemJS = new sn_sc.CatItem(itemRecord.getUniqueValue());
if (!catalogItemJS.canView())
continue;

var catItemDetails = catalogItemJS.getItemSummary();
var item = {};
item.name = catItemDetails.name;
item.short_description = catItemDetails.short_description;
item.picture = catItemDetails.picture;
item.price = catItemDetails.price;
item.sys_id = catItemDetails.sys_id;
item.hasPrice = catItemDetails.show_price;
item.page = 'sc_cat_item';
item.type = catItemDetails.type;
item.order = catItemDetails.order;
item.sys_class_name = catItemDetails.sys_class_name;
if (item.type == 'order_guide') {
item.page = 'sc_cat_item_guide';
} else if (item.type == 'content_item') {
item.content_type = catItemDetails.content_type;
item.url = catItemDetails.url;
if (item.content_type == 'kb') {
item.kb_article = catItemDetails.kb_article;
item.page = 'kb_article';
} else if (item.content_type == 'external') {
item.target = '_blank';
}
}
items.push(item);
}
}

function queryItems(catalog, categoryId, startWindow, endWindow) {
var scRecord = new sn_sc.CatalogSearch().search(catalog, categoryId, '', false, options.show_items_from_child != 'true');
scRecord.addQuery('sys_class_name', 'NOT IN', 'sc_cat_item_wizard');
scRecord.addEncodedQuery('hide_sp=false^ORhide_spISEMPTY^visible_standalone=true');
scRecord.chooseWindow(startWindow, endWindow);
scRecord.orderBy('order');
scRecord.orderBy('name');
scRecord.query();
return scRecord;
}

function getPopularItems() {
var limit = 6;
var items = [];
var allowedItems = getAllowedCatalogItems();

var createdQuery = '';
if (options.popular_items_created == 3)
createdQuery = 'sys_created_onONLast 3 months@javascript:gs.beginningOfLast3Months()@javascript:gs.endOfLast3Months()^';
else if (options.popular_items_created == 6)
createdQuery = 'sys_created_onONLast 6 months@javascript:gs.beginningOfLast6Months()@javascript:gs.endOfLast6Months()^';
else if (options.popular_items_created == 12)
createdQuery = 'sys_created_onONLast 12 months@javascript:gs.beginningOfLast12Months()@javascript:gs.endOfLast12Months()^';

var count = new GlideAggregate('sc_req_item');
count.addAggregate('COUNT', 'cat_item');
count.groupBy('cat_item.sys_id');
count.addQuery('cat_item.sys_class_name', 'NOT IN', 'sc_cat_item_guide,sc_cat_item_wizard,sc_cat_item_content,sc_cat_item_producer');
count.addQuery('cat_item', "IN", allowedItems);
count.addQuery('cat_item.visible_standalone', 'true');
count.addEncodedQuery(createdQuery + 'cat_item.hide_sp=false^ORcat_item.hide_spISEMPTY');
count.orderByAggregate('COUNT', 'cat_item');
count.query();
while (count.next() && items.length < limit) {
var catalogItemJS = new sn_sc.CatItem(count.getValue("cat_item.sys_id"));
if (!catalogItemJS.canView() || !catalogItemJS.isVisibleServicePortal())
continue;
var item = {};
var catItemDetails = catalogItemJS.getItemSummary();

item.order = 0 - count.getAggregate('COUNT', 'cat_item');
item.name = catItemDetails.name;
item.short_description = catItemDetails.short_description;
item.picture = catItemDetails.picture;
item.price = catItemDetails.price;
item.sys_id = catItemDetails.sys_id;
item.hasPrice = item.price != 0;
item.page = 'sc_cat_item';
item.favorite = (arrayUtil.contains(data.favCatItems, item.sys_id) ? true : false);
items.push(item);
}
var producers = 0;
count = new GlideAggregate('sc_item_produced_record');
count.addQuery('producer', "IN", allowedItems);
count.addEncodedQuery(createdQuery + 'producer.hide_sp=false^ORproducer.hide_spISEMPTY');
count.addAggregate('COUNT', 'producer');
count.addQuery('producer.visible_standalone', 'true');
count.groupBy('producer.sys_id');
count.orderByAggregate('COUNT', 'producer');
count.query();
while (count.next() && producers < limit) {
var catalogItemJS = new sn_sc.CatItem(count.getValue('producer.sys_id'));
if (!catalogItemJS.canView() || !catalogItemJS.isVisibleServicePortal())
continue;
var catItemDetails = catalogItemJS.getItemSummary();
var item = {};
item.order = 0 - count.getAggregate('COUNT', 'producer');
item.name = catItemDetails.name;
item.short_description = catItemDetails.short_description;
item.picture = catItemDetails.picture;
item.price = catItemDetails.price;
item.hasPrice = item.price != 0;
item.sys_id = catItemDetails.sys_id;
item.page = 'sc_cat_item';
item.favorite = (arrayUtil.contains(data.favCatItems, item.sys_id) ? true : false);
items.push(item);
producers++;
}
return items;
}

function getAllowedCatalogItems() {
var allowedItems = [];
catalogIDArr.forEach(function(catalogID) {
var catalogObj = new sn_sc.Catalog(catalogID);
var catItemIds = catalogObj.getCatalogItemIds();
for (var i = 0; i < catItemIds.length; i++) {
if (!allowedItems.includes(catItemIds[i]))
allowedItems.push(catItemIds[i]);
}
});
return allowedItems;
}

})();

 

Client script:

 

function($scope, spUtil, spScUtil, i18n, $rootScope, $location, $timeout, $window) {
$scope.hideItemWidget = !$scope.data.category_id;
if ($scope.data.category) {
if (!$scope.data.categories)
$scope.data.categories = [];
$scope.data.categories.forEach(function(category, index, categories) {
categories[index].url = category.url + '&catalog_id=' + $scope.data.catalog_id;
});
if ($scope.data.sc_catalog)
$scope.data.categories.unshift({
label: $scope.data.sc_catalog,
url: '?id=' + $scope.data.sc_category_page + '&catalog_id=' + $scope.data.catalog_id
});
if ($scope.data.show_popular_item) {
if ($scope.data.all_catalog_msg)
$scope.data.categories.unshift({
label: $scope.data.all_catalog_msg,
url: '?id=' + $scope.data.sc_category_page + '&catalog_id=-1'
});
else
$scope.data.categories.push({
label: $scope.data.all_cat_msg,
url: '#'
});
} else {
if ($scope.data.all_catalog_msg) {
$scope.data.categories.unshift({
label: $scope.data.all_catalog_msg,
url: '?id=' + $scope.data.sc_category_page + '&catalog_id=-1'
});
}
$scope.data.categories.push({
label: $scope.data.category.title,
url: '#'
});
}
$timeout(function() {
$rootScope.$broadcast('sp.update.breadcrumbs', $scope.data.categories);
});
spUtil.setSearchPage('sc');
}
$scope.showCategories = function() {
$scope.hideItemWidget = true;
$rootScope.$broadcast("$sp.service_catelog.show.categories_widget");
}

var listenerForCatItems = $scope.$on("$sp.service_catelog.show.items_widget", function() {
$scope.hideItemWidget = false;
});
/*=============== Begin link handling ===============*/

$scope.changeView = function(view) {
spScUtil.setPreference('catalog-item-list-view', view)
$scope.view = view;
}

spScUtil.getPreference('catalog-item-list-view', function(value) {
$scope.view = value || 'card';
});

$scope.isTouchDevice = function() {
return ('ontouchstart' in $window);
}

$scope.loadMore = function() {
$scope.data.new_limit = $scope.data.limit + ($scope.options.limit_item || 9);
$scope.stopLoader = false;
$scope.server.update().then(function() {
var old_limit = $scope.data.limit - ($scope.options.limit_item || 9);
$scope.data.items[old_limit].highlight = true;
$scope.stopLoader = true;
});
}

$scope.onClick = function($event, item) {
window.GlideWebAnalytics.trackEvent("Service Catalog", "Catalog Browse", "Item Clicked");
var lp = getLinkParts(item);
if (typeof lp == "string") {
$window.open(lp, '_blank');
return;
}
$event.stopPropagation();
$event.preventDefault();
var evt = {
item: item,
search: lp
};
// This will let a wrapper widget intercept and redirect somewhere else
$scope.$emit($scope.options.click_event_name, evt);
};

function getLinkParts(item) {
if (item.sys_class_name == 'sc_cat_item_content' && item.content_type == 'external')
return item.url;

return {
id: item.page,
sys_id: item.content_type == 'kb' ? item.kb_article : item.sys_id,
sysparm_category: $scope.data.category_id,
referrer: (item.content_type != 'kb' && $scope.data.show_popular_item) ? 'popular_items' : null
};
}

$scope.getItemHREF = function(item) {
var lp = getLinkParts(item);
if (typeof lp == "string")
return lp;
return "?id=" + lp.id + "&sys_id=" + lp.sys_id + "&sysparm_category=" + $scope.data.category_id + ($scope.data.catalog_id ? "&catalog_id=" + $scope.data.catalog_id : "");
}

var unregister = $rootScope.$on($scope.options.click_event_name, function($event, o) {
if ("url" in o)
$location.href = o.url;
else
$location.search(o.search);
});

if ($scope.options.isServiceWorkspace) {
$window.postMessage({
msg: 'CATALOG_ITEM_SET_TITLE',
title: i18n.getMessage('Catalogs')
}, $location.origin);
}

var mql;

if ($window.matchMedia) {
mql = $window.matchMedia('screen and (max-width: 768px)');
mql.addListener(handleMatchMedia);
handleMatchMedia(mql);
}

function handleMatchMedia(mql) {
if (!mql.matches) {
spScUtil.getPreference('catalog-item-list-view', function(value) {
$scope.view = value || 'card';
$scope.isMobile = false;
});
} else {
$timeout(function() {
$scope.view = 'card';
$scope.isMobile = true;
})
}
}
$scope.$on("$destroy", function() {
unregister();
listenerForCatItems();
if (mql)
mql.removeListener(handleMatchMedia);
});

$scope.startItemList = function() {
$scope.stopLoader = true;
}
var c=this;
$scope.toggleFavorite = function(item) {
c.server.get({
action: "toggleFavorite",
sys_id: item.sys_id,
addFav: (!item.favorite ? true : false),
}).then(function(response) {
item.favorite = !item.favorite;
$rootScope.$broadcast("<customer/project prefix>.update.catitemfavorites");
});
};
$rootScope.$on("<customer/project prefix>.remove.catitemfavorites", function() {
c.server.update();
});

/*=============== End link handling ===============*/
}