How to make Typeahead Search in Service Portal show Service Catalog Items when not logged in?

sethhumphrey
Mega Guru

Our Service Portal is very public by design.  We just discovered that searching in the Typeahead Search widget will not yield results from the Service Catalog unless you're logged in.  How do I fix that so it will show catalog item matches regardless if you're logged in?  In cloning the widget I don't see where that's being set.

find_real_file.png

You may be asking yourself, "why show something that don't have access to anyway?"  Our strategy is to heavily use Content Items to populate our Service Catalog so the SC is the full picture of the services we provide whether you have to request them or not.  Requestable items are visible but a user must login to order the service (see graphic).

find_real_file.png

1 ACCEPTED SOLUTION

Yes!  Sorry I never came back to update this issue.  Here is the Typeahead Search code that works for me:

 

HTML

<form ng-submit="c.submitSearch()">
  <input type="hidden" name="id" value="search"/>
  <input type="hidden" name="t" value="{{data.searchType}}"/>
  <div class="input-group input-group-{{::c.options.size}}">
    <!-- uses ui.bootstrap.typeahead -->
    <!-- replaced `uib-typeahead="item for item in c.getResults($viewValue)"`
         in the element below to fix the [object Object] issue -->
    <input id="search-input"
           name="q" type="text" placeholder="{{::c.options.title}}" ng-model="c.selectedState"
           ng-model-options="{debounce: 250}" autocomplete="off"
     			 uib-typeahead="item as item.label for item in c.getResults($viewValue)"
           typeahead-focus-first="false"
           typeahead-on-select="c.onSelect($item, $model, $label)"
           typeahead-template-url="sp-typeahead-auburn.html"
           typeahead-popup-template-url="sp-typeahead-popup-auburn.html"
           class="form-control input-typeahead"
           title="{{::c.options.title}}"
           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.options.title}}"
              aria-label="{{::c.options.title}}">
    	<i ng-if="::c.options.glyph" class="fa fa-{{::c.options.glyph}}"></i>
      </button>
    </span>
  </div>
</form>

CSS

ul.dropdown-menu {
    min-width: 100%;
    border-radius: 0px 0px 4px 4px;
    margin:0px;
}

ul.dropdown-menu a.ta-item {
	line-height: 20px;
}
ul.dropdown-menu li h4 {
  background-color:#efefef;
  margin-left:0;
  padding:10px;
}
span.ta-item-header{
     -moz-user-select: -moz-none;
   -khtml-user-select: none;
   -webkit-user-select: none;
   -o-user-select: none;
   user-select: none;
  pointer-events:none;
}
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;
    float:left;
    margin-right: 8px;
}

input[name="q"] {
color: black;
}

.ta-item-header h4 {
  margin-left: 7px;
}

.no-result {
 margin-left: 7px;
 padding-left: 20px;
}

Client Script

function ($filter, $location,spAriaUtil, $window) {
	
	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.onSelect = function($item, $model, $label) {
		if ($item.target)
			window.open($item.url, $item.target);
		else
			$location.url($item.url);
	};

	c.getResults = function(query) {
		return c.server.get({q: query}).then(function(response) {
			var a = $filter('orderBy')(response.data.results, '-order');
			var resultCount = $filter('limitTo')(a, c.data.limit).length;
				spAriaUtil.sendLiveMessage(parseInt(resultCount) + " " + 
																	 c.data.resultMsg + " " +
																	 c.data.navigationMsg + 
																	 getNavigationKeys());
			return $filter('limitTo')(a, c.data.limit);
		});
	}

	c.submitSearch = function() {
		if (c.selectedState) {
			$location.search({
				id: 'search',
				t: c.data.searchType,
				q: c.selectedState
			});
			console.log(c.selectedState);
		}
	}
	
	function getNavigationKeys() {
		if($window.navigator.userAgent.indexOf("Mac OS X") > -1) 
			return '⌘';
		return 'Control';
	}
}

Server Script

(function() {

//var order = 0;// ordering articles and catalog
data.searchType = $sp.getParameter("t");
data.results = [];
data.searchMsg = gs.getMessage("Search");
data.limit = options.limit || 15;
var order = data.limit;

var textQuery = '123TEXTQUERY321';

if (!input)
return;

data.q = input.q;

getCatalogItems();
getKnowledge();

// add in additional search tables from sp_search_groups
var portalGR = $sp.getPortalRecord();
var portalID = portalGR.getDisplayValue('sys_id');
var sg = GlideRecord('sp_search_group');
sg.addQuery('sp_portal',portalID);
sg.addQuery('active',true);
sg.orderBy('order');
sg.query();

while (sg.next())
    addSearchTable(sg);

// typeahead search generates multiple "Your text query contained only
// common words..." msgs, we don't want them
gs.flushMessages();

function addSearchTable(sg) {
    var table = sg.getValue('name');
    var condition = sg.getValue('condition');
    var gr = GlideRecord(table);

    if (condition)
        gr.addEncodedQuery(condition);

    gr.addQuery(textQuery, data.q);
    gr.query();

    var searchTableCount = 10; // serach count

    var header = {};     // begin header label for catalog and article

    header.type="header";
    header.label = "<h4>"+sg.tables.getDisplayValue()+"</h4>";
    header.score = 0;
    header.order=order;
    header.text= sg.tables.getDisplayValue();
    data.results.push(header);
    //order++;     // end
		order--;

    while (gr.next() && searchTableCount < data.limit) {
        var rec = {};
        rec.type = "rec";
        rec.table = table;
        rec.sys_id = gr.getDisplayValue('sys_id');
        rec.page = sg.getDisplayValue('sp_page');

        if (!rec.page)
            rec.page = "form";

        rec.label =     "<b>"+table+"</b> "+gr.getDisplayValue(); // displaying label 
        rec.score = parseInt(gr.ir_query_score.getDisplayValue());
        rec.order=order;   // order begin
        data.results.push(rec);
        searchTableCount++;
        //order++;   // order   end 
				order--;
    } 
}




function getKnowledge() {
    var kb = new GlideRecord('kb_knowledge');
    kb.addQuery('workflow_state', 'published');
    kb.addQuery('valid_to', '>=', (new GlideDate()).getLocalDate().getValue());
    kb.addQuery(textQuery, data.q);
    //kb.addQuery('kb_knowledge_base', $sp.getDisplayValue('kb_knowledge_base'));
    kb.addQuery('kb_knowledge_base.title', 'Information Technology');  
    kb.query();

    var kbCount = 10; // count list 

    var header = {}; // begin header label for   article                     

    header.type = "header";
    header.label = "<h4>Knowledge Base</h4>";
    header.score = 10;
    header.order = order;
    header.text = "Knowledge Article";
	
		header.hasItems = kb.hasNext();
	
    data.results.push(header);
    //order++; //order end
		order--;
	
		if (!kb.hasNext()) {
			header.label += '<i class="no-result">No results of this type.</i>';
		}
	
    while (kb.next() && kbCount < data.limit) {
        if (!$sp.canReadRecord(kb))
            continue;

        var article = {};

        article.type = "kb";
        $sp.getRecordDisplayValues(article, kb, 'sys_id,number,short_description,description,picture,published,text');
		
				if (!article.text)
            article.text = "";
			
				article.glyph = 'file-text-o';
        article.text = $sp.stripHTML(article.text);
        article.text = article.text.substring(0, 200);
        article.score = parseInt(kb.ir_query_score.getDisplayValue());
        article.label = article.short_description; // highlight KB in bold fornt of KB article
        article.order = order; // order begin
				article.url = '/it?id=' + 'kb_article' + '&sys_id=' + article.sys_id;
        data.results.push(article);
        kbCount++;
        //order++; // order ends
				order--;
    }
}

function getCatalogItems() {
    var sc = new GlideRecord('sc_cat_item');
    sc.addQuery(textQuery, data.q);
    sc.addQuery('active', true);
    sc.addQuery('no_search', '!=', true);
    sc.addQuery('visible_standalone', true);
    sc.addQuery('sys_class_name', 'NOT IN', 'sc_cat_item_wizard');
    sc.addQuery('sc_catalogs', $sp.getValue('sc_catalog'));
    sc.query();

    var catCount = 10; // limit the search to 5
    var header = {}; // begin of header and label for catalog 

    header.type = "header";
    header.label = "<h4>Service Catalog</h4>";
    header.score = 10;
    header.order = order;
    header.text = "Service Catalog";
	
    data.results.push(header);
    //order++;
		order--;

		if (!sc.hasNext()) {
			header.label += '<i class="no-result">No results of this type.</i>';
		}
	
    while (sc.next() && catCount < data.limit) {
        if (!$sp.canReadRecord(sc))
            continue;

        var item = {};
			
				item.fullResult = $sp;
			
        if (sc.getRecordClassName() == "sc_cat_item_guide")
            item.type = "sc_guide";

        else if (sc.getRecordClassName() == "sc_cat_item_content") {
            var gr = new GlideRecord('sc_cat_item_content');
            gr.get(sc.getUniqueValue());
            $sp.getRecordValues(item, gr, 'url,content_type,kb_article');
            item.type = "sc_content";
        } else
            item.type = "sc";
				
        $sp.getRecordDisplayValues(item, sc, 'name,short_description,description,picture,price,sys_id');
        item.score = parseInt(sc.ir_query_score.getDisplayValue());
        item.order = order; //order begin
      
				item.label = item.name; // highlight SC in bold infront of catlog name ("Service Catalog "+)
			
				var urlid;
				if (sc.getRecordClassName() === 'sc_cat_item_guide')
					urlid = 'sc_cat_item_guide';
				else
					urlid = 'sc_cat_item'
			
				item.url = '/it?id=' + urlid + '&sys_id=' + item.sys_id;
				data.results.push(item);
        catCount++;
        //order++; // order ends
				order--;
    }
}

function getQuestions() {
    var questionGR = new GlideRecord("kb_social_qa_question");
    questionGR.addActiveQuery();
    questionGR.addQuery(textQuery, data.q);
    questionGR.query();
    var qCount = 0;
    var header = {}; // begin of header for questions 
    header.type = "header";
    header.label = "<h4>Questions</h4>";
    header.score = 0;
    header.order = order;
    header.text = "Questions";
    data.results.push(header);
    //order++;
		order--;

    while (questionGR.next() && qCount < data.limit) {
        if (!$sp.canReadRecord(questionGR))
            continue;

        var question = {};
        question.type = "qa";
        $sp.getRecordDisplayValues(question, questionGR, 'question,question_details,sys_created_on,sys_id');
        question.text = (question.question_details) ? $sp.stripHTML(question.question_details) : "";
        question.text = question.text.substring(0, 200);
        question.label = "<b>Question</b> " + question.question;
        question.score = 0;
        question.order = order; //order begin
        data.results.push(question);
        qCount++;
        //order++; // order ends
				order--;
    }
}

})();

Link Function

function(scope) {
	var lazyLoader = $injector.get("lazyLoader");
	lazyLoader.putTemplates(scope.data.typeaheadTemplates);
}

Template - sp-typeahead-popup-auburn.html

<ul class="dropdown-menu" ng-show="isOpen() && !moveInProgress" ng-style="{top: position().top+'px', left: position().left+'px'}" role="listbox" aria-hidden="{{!isOpen()}}">
    <li role='option' aria-hidden='true' style='display: none'></li>    
  	<li ng-repeat="match in matches track by $index" ng-class="{active: isActive($index)}" 
        ng-mouseenter="selectActive($index)" 
        ng-click="match.model.type === 'header' ? '' : selectMatch($index, $event)"
        role="option" id="{{::match.id}}">
        <div uib-typeahead-match index="$index" match="match" query="query" template-url="templateUrl"></div>
    </li>
</ul>

Template - sp-typeahead-auburn.html

<!-- replaced instances `match.label.label` with `match.label` [object Object] issue,
     since we changed the value of the `uib-typeahead` attribute in the HTML template -->

<!-- added to show category headers as simple spans instead of a elements -->
<span class="ta-item-header ta-item ng-scope" 
      ng-if="match.model.type === 'header'" ng-bind-html="match.label">
</span>

<!--<a class="ta-item" ng-href="{{match.model.target != '_blank' ? match.model.url : ''}}" target="{{match.model.target}}">-->
<a ng-if="match.model.type !== 'header'" class="ta-item" ng-href="{{ match.model.url }}" target="{{match.model.target}}">
	<div ng-if="!match.model.templateID">
    <!-- if service catalog and has icon -->
    <i class="ta-img ng-scope" ng-if="match.model.type.includes('sc') && match.model.picture.length > 0" 
       ng-style="{'background-image':'url({{match.model.picture}})'}"></i>
    <!-- if service catalog and has no icon -->
    <i class="ta-img ng-scope ta-icon fa fa-file-text-o" ng-if="match.model.type.includes('sc') && match.model.picture.length === 0"></i>
    <!-- if knowledge base -->
    <i class="ta-img ng-scope ta-icon fa fa-file-text-o" ng-if="match.model.type.includes('kb')"></i>
    <span ng-bind-html="match.label | uibTypeaheadHighlight:query"></span>
  </div>
  <div ng-if="match.model.templateID" ng-include="match.model.templateID"></div>
</a>

View solution in original post

10 REPLIES 10

Bhojraj Dhakate
Tera Expert

Hi,

Please check the following check boxes from the landing pages and the "typeahead search"widget:

1. Check public checkbox :

find_real_file.png

 2. check public checkbox from the widget:

find_real_file.png

 

I am able to search from "Typeahead search widget".

 

find_real_file.png

 

Thanks, Bhojraj

Thanks for the suggestion Bhoraj but these settings are already in place.

find_real_file.png

 

The issue isn't being able to search, it's just the search results don't show Service Catalog Items unless you're logged in.

Community Alums
Not applicable

Hi I have one doubt - how to limit the search text like it allows only limited (20 or 15 or 10) characters on search drop down(texts which are too big so that it goes outside of white portion of Search).

please see the screen shoot, Could you please let me know ASAP.

find_real_file.png

gml35
Tera Guru

Hey Seth,

 

Did you ever get this sorted out?  We're in the middle of a huge push to get CSM up and running and we're hitting the same limitation.