Developer Examples (Cont)
Change component placement
In some cases, a component or set of components must be placed in a different position in the layout. For this example, we assume the end goal is to migrate the Selections component from the top of Results to the top of the FacetList component. This will be done by creating a widget template that is extending two templates - for the facets and for the results. All default templates are available on vue-hawksearch/src/components at master · hawksearch/vue-hawksearch in each component file in the <template>
. The provided samples in this article are based on those templates. Similar component migrations can be done the same way between any given set of components.
Steps to extend
- Navigate to the directory of the currently active widget template (e.g. Mvc\Views\Hawksearch\Hawksearch.Default.cshtml or the relevant resource package).
- Copy the file of the base template or select an existing one.
- Add the template override for the FacetList. In this case, the only modification is adding the Selections component on 4
<script id="vue-hawksearch-facet-list" type="x-template">
<div v-if="!waitingForInitialSearch" class="hawk-facet-rail">
<div class="hawk-facet-rail__heading">{{ $t('Narrow Results') }}</div>
<selections />
<div class="hawk-facet-rail__facet-list">
<template v-if="facets && facets.length">
<facet v-for="facetData in facets" :key="facetData.FacetId" :facet-data="facetData"></facet>
</template>
<template v-else-if="loadingResults">
<placeholder-facet v-for="index in 4" :key="index"></placeholder-facet>
</template>
<template v-else="">
<div class="hawk-facet-rail_empty"></div>
</template>
</div>
</div>
</script>
For additional info visit the original component file: https://github.com/hawksearch/vue-hawksearch/blob/master/src/components/facets/FacetList.vue
- The next step is removing the Selections component from the Results. The override is basically similar to the default template, only the tag is omitted. Original component file: https://github.com/hawksearch/vue-hawksearch/blob/master/src/components/results/Results.vue
<script id="vue-hawksearch-results" type="x-template">
<div class="hawk-results" v-on:click="onClick">
<autocorrect-suggestions />
<search-results-label />
<banner></banner>
<template v-if="searchError">
<span>{{ $t('response_error_generic') }}</span>
</template>
<template v-else-if="searchOutput && searchOutput.Results && searchOutput.Results.length == 0">
<span>{{ $t('No Results') }}</span>
</template>
<template v-else-if="!waitingForInitialSearch">
<tabs></tabs>
<div class="hawk-results__top-tool-row">
<tool-row />
</div>
<result-listing />
<div class="hawk-results__bottom-tool-row">
<tool-row />
</div>
</template>
</div>
</script>
- Include the file in the project.
- Save, build and reload the site.
Change the autocomplete results based on content type
Goal
This article provides information on how to override the SuggestionItem component template in order to change the displayed results based on their content type.
Steps to override
- Open the widget template of either the Hawksearch box or the Hawksearch results widget.
- Paste the following code snippet in the Vue part of the template.
- Save the template.
- Open the page in the frontend.
- Type in the search box to view your new autocomplete results template.
If you are unfamiliar with Vue, please refer to the ready-to-use example at the end of the article.
<script id="vue-hawksearch-suggestion-item" type="x-template">
<li v-on:click="onClick">
<template v-if="getField('contenttype') == 'Telerik.Sitefinity.Pages.Model.PageNode'">
<h3>{{ getField('customTitle') }}</h3>
</template>
<template v-else-if="getField('contenttype') == 'Telerik.Sitefinity.News.Model.NewsItem'">
<h3>{{ getField('title') }}</h3>
<h3>{{ getField('lastmodified') }}</h3>
</template>
<template v-else>
<h3>{{ getField('title') }}</h3>
</template>
</li>
</script>
Component overview
The component checks the content type of the document by accessing the ContentType field with the help of the getField('contenttype')
method. This field is one of the several default fields which are indexed in Hawksearch. If you wish to index custom fields such as the customTitle on line 4 please refer to the documentation - Extending Data Indexing in Hawksearch V4. If the document is neither a page nor a news article, then just the title is displayed.
All indexed fields are accessible through getField('fieldName').
Ready-to-use examples
HawksearchBox
@model HawksearchWidgets.Mvc.ViewModels.HawksearchBox.SearchBoxViewModel
@using Telerik.Sitefinity.Frontend.Mvc.Helpers;
@using Telerik.Sitefinity.Modules.Pages
@using Telerik.Sitefinity.Services
@Html.Script(Url.WidgetContent("assets/build/js/vendor.bundle.js"), "bottom", false)
@Html.Script(Url.WidgetContent("assets/build/js/main.js"), "bottom", false)
@Html.StyleSheet(Url.WidgetContent("assets/build/css/vendor.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/build/css/main.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/vue-hawksearch.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/vue-hawksearch-override.css"), "head")
<div data-component="vue-app-searchbox"
data-client-guid="@Model.ClientId"
data-hawksearch-tracking-api="@Model.TrackingUrl"
data-hawksearch-base-api="@Model.HawksearchBaseAPI"
data-hawksearch-search-api="@Model.HawksearchSearchingAPI"
data-hawksearch-autocomplete-api="@Model.AutocompleteUrl"
data-tracking-events="@Model.TrackingEvents"
data-search-page="@Model.ResultsUrl"
data-show-searchbox="True"
data-json-params="@Model.Data"
data-index-name="@Model.Index"
data-hawksearch-data-layer="@Model.WidgetBinding"
data-current-culture="@Model.CurrentCulture">
<div class="hawk">
<div class="hawk__header">
<div data-component="hawksearch-field">
<search-box search-page="@Model.ResultsUrl"></search-box>
</div>
</div>
</div>
</div>
//Autocomplete component override
<script id="vue-hawksearch-suggestion-item" type="x-template">
<li v-on:click="onClick">
<template v-if="getField('contenttype') == 'Telerik.Sitefinity.Pages.Model.PageNode'">
<h3>{{ getField('title') }}</h3>
</template>
<template v-else-if="getField('contenttype') == 'Telerik.Sitefinity.News.Model.NewsItem'">
<h3>{{ getField('customTitle') }}</h3>
</template>
<template v-else>
<h3>{{ getField('title') }}</h3>
</template>
</li>
</script>
<script data-translations="vue-translations" type="application/json">
{
"Narrow Results": "@Html.HtmlSanitize(Html.Resource("NarrowResults", "HawkWidgetsResources"))",
"Search Results": "@Html.HtmlSanitize(Html.Resource("SearchResults", "HawkWidgetsResources"))",
"Search Results for": "@Html.HtmlSanitize(Html.Resource("SearchResultsFor", "HawkWidgetsResources"))",
"Sort By": "@Html.HtmlSanitize(Html.Resource("SortBy", "HawkWidgetsResources"))",
"Enter a search term": "@Html.HtmlSanitize(Html.Resource("EnterKeyword", "HawkWidgetsResources"))",
"Quick Lookup": "@Html.HtmlSanitize(Html.Resource("QuickLookup", "HawkWidgetsResources"))",
"Clear All": "@Html.HtmlSanitize(Html.Resource("ClearAll", "HawkWidgetsResources"))",
"Clear": "@Html.HtmlSanitize(Html.Resource("Clear", "HawkWidgetsResources"))",
"No Results": "@Html.HtmlSanitize(Html.Resource("NoResults", "HawkWidgetsResources"))",
"Loading": "@Html.HtmlSanitize(Html.Resource("Loading", "HawkWidgetsResources"))",
"You've Selected": "@Html.HtmlSanitize(Html.Resource("YouSelected", "HawkWidgetsResources"))",
"response_error_generic": "An error occurred while searching for your results. Please contact the site administrator."
}
</script>
Hawksearch results
If you are using the search box from the results widgets please refer to the example below.
@model HawksearchWidgets.Mvc.ViewModels.Hawksearch.SearchViewModel
@using Telerik.Sitefinity.Frontend.Mvc.Helpers;
@using Telerik.Sitefinity.Services
@Html.Script(Url.WidgetContent("assets/build/js/vendor.bundle.js"), "bottom", false)
@Html.Script(Url.WidgetContent("assets/build/js/main.js"), "bottom", false)
@Html.StyleSheet(Url.WidgetContent("assets/build/css/vendor.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/build/css/main.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/vue-hawksearch.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/vue-hawksearch-override.css"), "head")
<div data-component="vue-app-spa"
data-client-guid="@Model.ClientId"
data-hawksearch-tracking-api="@Model.TrackingUrl"
data-hawksearch-base-api="@Model.HawksearchBaseAPI"
data-hawksearch-search-api="@Model.HawksearchSearchingAPI"
data-hawksearch-autocomplete-api="@Model.AutocompleteUrl"
data-tracking-events="@Model.TrackingEvents"
data-index-name="@Model.HawksearchIndexName"
data-json-params="@Model.Data"
data-current-culture="@Model.CurrentCulture"
data-hawksearch-recommendation-api=""
data-widget-guid=""
data-hawksearch-data-layer="@Model.WidgetBinding"
data-show-searchbox="@Model.ShowSearchBox.ToString()">
<div class="hawk">
@if (Model.ShowSearchBox)
{
<div class="hawk__header">
<div data-component="hawksearch-field">
<search-box></search-box>
</div>
</div>
}
<div class="hawk__body">
<div data-component="hawksearch-facets">
<facet-list></facet-list>
</div>
<div data-component="hawksearch-results">
<results></results>
</div>
</div>
</div>
</div>
<script id="vue-hawksearch-result-item" type="x-template">
<div class="media-body sf-media-body" v-on:click="onClick">
<h3>
<template v-if="link">
<a :href=link>{{ title }}</a>
</template>
<template v-else>
{{ title }}
</template>
</h3>
<p>
<strong class="sfHighlight">{{ title }}</strong>
<span>{{ content }}</span>
</p>
<a :href="link">{{ link }}</a>
</div>
</script>
//Autocomplete component override
<script id="vue-hawksearch-suggestion-item" type="x-template">
<li v-on:click="onClick">
<template v-if="getField('contenttype') == 'Telerik.Sitefinity.Pages.Model.PageNode'">
<h3>{{ getField('title') }}</h3>
</template>
<template v-else-if="getField('contenttype') == 'Telerik.Sitefinity.News.Model.NewsItem'">
<h3>{{ getField('customTitle') }}</h3>
</template>
<template v-else>
<h3>{{ getField('title') }}</h3>
</template>
</li>
</script>
<script data-translations="vue-mappings" type="application/json">
{
"Telerik.Sitefinity.Events.Model.Event": "@Html.HtmlSanitize(Html.Resource("EventTypeDisplayName", "HawkWidgetsResources"))",
"Telerik.Sitefinity.Libraries.Model.Image": "@Html.HtmlSanitize(Html.Resource("ImageTypeDisplayName", "HawkWidgetsResources"))",
"Telerik.Sitefinity.Libraries.Model.Video": "@Html.HtmlSanitize(Html.Resource("VideoTypeDisplayName", "HawkWidgetsResources"))",
"Telerik.Sitefinity.Libraries.Model.Document": "@Html.HtmlSanitize(Html.Resource("DocumentTypeDisplayName", "HawkWidgetsResources"))",
"Telerik.Sitefinity.News.Model.NewsItem": "@Html.HtmlSanitize(Html.Resource("NewsItemDisplayName", "HawkWidgetsResources"))",
"Telerik.Sitefinity.Blogs.Model.BlogPost": "@Html.HtmlSanitize(Html.Resource("BlogPostDisplayName", "HawkWidgetsResources"))",
"Telerik.Sitefinity.Lists.Model.ListItem": "@Html.HtmlSanitize(Html.Resource("ListItemDisplayName", "HawkWidgetsResources"))",
"Telerik.Sitefinity.Pages.Model.PageNode": "@Html.HtmlSanitize(Html.Resource("PageNodeDisplayName", "HawkWidgetsResources"))"
}
</script>
<script data-translations="vue-translations" type="application/json">
{
"Narrow Results": "@Html.HtmlSanitize(Html.Resource("NarrowResults", "HawkWidgetsResources"))",
"Search Results": "@Html.HtmlSanitize(Html.Resource("SearchResults", "HawkWidgetsResources"))",
"Search Results for": "@Html.HtmlSanitize(Html.Resource("SearchResultsFor", "HawkWidgetsResources"))",
"Sort By": "@Html.HtmlSanitize(Html.Resource("SortBy", "HawkWidgetsResources"))",
"Enter a search term": "@Html.HtmlSanitize(Html.Resource("EnterKeyword", "HawkWidgetsResources"))",
"Quick Lookup": "@Html.HtmlSanitize(Html.Resource("QuickLookup", "HawkWidgetsResources"))",
"Clear All": "@Html.HtmlSanitize(Html.Resource("ClearAll", "HawkWidgetsResources"))",
"Clear": "@Html.HtmlSanitize(Html.Resource("Clear", "HawkWidgetsResources"))",
"No Results": "@Html.HtmlSanitize(Html.Resource("NoResults", "HawkWidgetsResources"))",
"Loading": "@Html.HtmlSanitize(Html.Resource("Loading", "HawkWidgetsResources"))",
"You've Selected": "@Html.HtmlSanitize(Html.Resource("YouSelected", "HawkWidgetsResources"))",
"response_error_generic": "An error occurred while searching for your results. Please contact the site administrator."
}
</script>
How to resolve search term
Goal
The keyword that users search for is displayed above the search results. But this information can be visualized in different places on the page. This article aims to explain how the search term/result can be placed in a different position within the page.
Steps to place the search term
- For visualizing the search term the CustomResultsLabel component could be used. This component can show the search term like it is shown below.
- Adding this label can be made easy by overriding the component where the search label should be visualized. Here is an example of how to override the suggestion component and adding the CustomResultsLabel.
- Navigate to the directory of the currently active widget template (e.g. Mvc\Views\Hawksearch\Hawksearch.Default.cshtml or the relevant resource package).
- Copy the file of the base template and name it accordingly (Hawksearch.ComponentName.cshtml).
- Edit the file adding or replacing the template override for the suggestion list component:
Add the where the search term should be visualized.
<script id="vue-hawksearch-suggestion-item" type="x-template">
<custom-results-label results-field="Keyword" />
<li v-on:click="onClick">
<template v-if="getField('contenttype') == 'Telerik.Sitefinity.Pages.Model.PageNode'">
<h3>{{getField('title')}}</h3>
</template>
<template v-else-if="getField('contenttype') == 'Telerik.Sitefinity.News.Model.NewsItem'">
<p>{{ getField('title') }}</p>
<small>{{ getField('lastmodified') }}</small>
</template>
<div v-html="item.Thumb"></div>
<p class="p-name">{{ item.ProductName }}</p>
</li>
</script>
- Include the file in the project.
- Save, build and reload the site.
For changing the template of the CustomResultsLabel, the component should be overridden.
This is a specific case of the CustomResultsLabel component usage. For more usage cases visit Example: Place a custom label with search response data
Place a custom label with search response data
Goal
Display any field from the search results as a custom label. This label can be used in any of the components and could fetch all field data associated with the current set parameters. For visualizing data in the layout, the CustomResultsLabel component is used. It will be placed in the extended template of the designated parent component. For this example - the Results component.
Steps to place the label
- Navigate to the directory of the currently active widget template (e.g. Mvc\Views\Hawksearch\Hawksearch.Default.cshtml or the relevant resource package).
- Copy the file of the base template and name it accordingly.
- Edit the file adding or replacing the template override for the Results component:
<script id="vue-hawksearch-results" type="x-template">
<div class="hawk-results" v-on:click="onClick">
<autocorrect-suggestions />
<search-results-label />
<banner></banner>
<selections />
<template v-if="searchError">
<span>{{ $t('response_error_generic') }}</span>
</template>
<template v-else-if="searchOutput && searchOutput.Results && searchOutput.Results.length == 0">
<span>{{ $t('No Results') }}</span>
</template>
<template v-else-if="!waitingForInitialSearch">
<tabs></tabs>
<div class="hawk-results__top-tool-row">
<tool-row />
</div>
<result-listing />
<div class="hawk-results__bottom-tool-row">
<tool-row />
</div>
</template>
</div>
</script>
- Add the where the response field should be visualized (ln.7). In this example, the number of results is displayed after the search results label. The name of the field is passed to the results-field prop (i.e. Pagination.NofResults). These values follow the same convention as the one in the result item: Example: Extending result item component to include a field from the response object
<script id="vue-hawksearch-results" type="x-template">
<div class="hawk-results" v-on:click="onClick">
<autocorrect-suggestions />
<search-results-label />
<custom-results-label results-field="Pagination.NofResults"></custom-results-label>
<banner></banner>
<selections />
<template v-if="searchError">
<span>{{ $t('response_error_generic') }}</span>
</template>
<template v-else-if="searchOutput && searchOutput.Results && searchOutput.Results.length == 0">
<span>{{ $t('No Results') }}</span>
</template>
<template v-else-if="!waitingForInitialSearch">
<tabs></tabs>
<div class="hawk-results__top-tool-row">
<tool-row />
</div>
<result-listing />
<div class="hawk-results__bottom-tool-row">
<tool-row />
</div>
</template>
</div>
</script>
- The end result so far is only the value of the field. To dynamically display additional labels around the required field, the component provides several slots. Currently, there are three available: before, after and default (i.e. instead) slot. With this example, the label will be either Found 12 results or No results found depending on the search output.
...
<custom-results-label results-field="Pagination.NofResults">
<template v-slot:before>
Found
</template>
<template v-slot:after>
results
</template>
<template v-slot>
No results found
</template>
</custom-results-label>
...
- Include the file in the project.
- Save, build and reload the site.
Enabling content in autocomplete
Overview
By default, the autocomplete suggestions are using products data. In addition to it, Hawksearch provides content data that can be enabled for the suggestions list. This is achieved by extending the default template for the SearchSuggestions component and the SuggestionItem component
Steps to extend
- Navigate to the directory of the currently active widget template (e.g. Mvc\Views\Hawksearch\Hawksearch.Default.cshtml or the relevant resource package).
- Copy the file of the base template or select an existing one.
- Edit the file adding or replacing the template override for the suggestion list component:
<script id="vue-hawksearch-search-suggestions" type="x-template">
<div class="hawk-autosuggest-menu">
<template v-if="fieldFocused && (loadingSuggestions || suggestions)">
<ul class="hawk-dropdown-menu hawk-autosuggest-menu__list hawk-autosuggest-outer-list">
<template v-if="loadingSuggestions">
<li class="hawk-autosuggest-menu__item">{{ $t('Loading') }}...</li>
</template>
<template v-else-if="suggestions.Content.length">
<ul class="hawk-autosuggest-inner-list">
<suggestion-item v-for="item in suggestions.Content" :item="item" :key="item.Results.DocId" v-on:itemselected="onItemSeleted"></suggestion-item>
</ul>
</template>
<template v-else>
<li class="hawk-autosuggest-menu__item">{{ $t('No Results') }}</li>
</template>
</ul>
</template>
</div>
</script>
- Edit the file adding or replacing the template override for the suggestion item component:
<script id="vue-hawksearch-suggestion-item" type="x-template">
<li v-on:click="onClick">
<p>{{ getField('title') }}</p>
</li>
</script>
- Save, build and reload the site.
Expose BestFragments fields
Steps to extend
- Navigate to the directory of the currently active widget template (e.g. Mvc\Views\Hawksearch\Hawksearch.Default.cshtml or the relevant resource package).
- Copy the file of the base template and name it accordingly.
- Edit the file adding or replacing the template override for the result item:
<script id="vue-hawksearch-result-item" type="x-template">
<div class="media-body sf-media-body">
<h3>
<template v-if="link">
<a :href=link>{{ title }}</a>
</template>
<template v-else>
{{ title }}
</template>
</h3>
<p>
<span>{{ result.BestFragments['itemname'] }}</span>
<span>{{ content }}</span>
</p>
<a :href="link">{{ link }}</a>
</div>
</script>
The key change here is the field reference to the BestFragments object (line 12). In this case, the itemname is displayed. Other available fields can be displayed in a similar manner.
- Include the file in the project and make it an Embedded resource in the Visual Studio solution.
- Save, build and reload the site.
- Open the widget designer for Hawksearch results widget on the designated page and select the newly created template from the select list. Save and publish the page.
Usage for multilingual
Multilingual support has structural alternations on the process of accessing fields, BestFragments included. Here are some additional steps that need to be executed for the exposed BestFragments fields to work in this scenario.
- Execute the aforementioned steps for the default template extending.
- Edit the template file and add the following script at the bottom of the file.
<script>try{window.addEventListener('load',function(){HawksearchVue.getLanguageSuffix=function(){return(s=Object.values(HawksearchVue.storeInstances))&&(cd=HawksearchVue.getClientData)&&s.length&&cd(s[0]).Custom&&cd(s[0]).Custom.language?('_'+cd(s[0]).Custom.language):''}})}catch(e){console.error(e)}</script>
- Update all field references to use the language suffix.
<script id="vue-hawksearch-result-item" type="x-template">
...
<span>{{ result.BestFragments['itemname' + HawksearchVue.getLanguageSuffix()] }}</span>
...
</script>
- Save, build and reload the site.
Override Results component using Slick.js carousel
Steps to override
- Navigate to the directory of the currently active widget template (e.g. Mvc\Views\Hawksearch\Hawksearch.Default.cshtml or the relevant resource package).
- Copy the file of the Default template and name it accordingly
- Ensure jQuery is loaded on the page:
@Html.Script(ScriptRef.JQuery, "top", false)
- Make sure Slick.js resources are loaded:
@Html.Script("https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.9.0/slick.min.js", "top", false)
@Html.StyleSheet("https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.9.0/slick-theme.min.css", "head")
@Html.StyleSheet("https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.9.0/slick.min.css", "head")
- Edit the file adding or replacing the template override for the result item:
<script id="vue-hawksearch-result-listing" type="x-template">
<div>
<template>
<div class="hawk-results__listing">
<template v-if="!loadingResults && results && results.length">
<div class="slider-area slider" style="width:700px">
<result-item v-for="result in results" :key="result.docid" :result="result"></result-item>
</div>
</template>
</div>
</template>
</div>
</script>
<script>
window.addEventListener('load', function () {
setTimeout(function () {
if (HawksearchVue && Object.values(HawksearchVue.widgetInstances).length) {
Object.values(HawksearchVue.widgetInstances)[0].$on('resultsupdate', function () {
setTimeout(function () {
$(".slider-area").slick({
dots: true,
infinite: false,
slidesToShow: 1,
slidesToScroll: 1,
});
}, 1)
})
}
}, 1)
})
</script>
Apply the desired structure in this format. All indexed fields are accessible through getField().
- Include the file in the project.
- Save, build and reload the site.
- Open the widget designer for Hawksearch results widget on the designated page and select the newly created template from the select list. Save and publish the page.
How to extend Filter by Permissions
Goal
The purpose of this article is to provide information on how to extend the Filter by Permissions functionality to hide specific fields depending on the user role.
Setup User and Role
First create a new role:
- Navigate to Administration → Roles (your-site-domain/Sitefinity/Administration/Roles)
- Press create and enter a name for the role e.g. NonMember
Now create a user:
- Navigate to Administration → Users (your-site-domain/Sitefinity/Administration/Users)
- Create a new user by filling out the form
- In roles section choose the newly created role (NonMember)
- Press create this user
Create content
The following steps demonstrate how to create dynamic content .
- Navigate to Administration → Module builder (your-site-domain/Administration/Module-builder)
- Create a module e.g. Books.
- Add fields e.g. Author, Description, Pages, Price
- Press finish and activate the module
- Under Content find Books and create a couple of them
Setup Index
- Navigate to Administration → Search indexes
- Create a search index
- Under advanced add the additional field you wish to index e.g. Price
- Save the changes and reindex
Extend filtering logic
In order to extend the filtering logic you need to inherit from the SearchApiModel class and and override the
AutocompleteResult GetAutocompleteSecuredResult(AutocompleteQuery query)
andSearchResult GetSearchSecuredResult(SearchQuery query)
.
The following code snippet demonstrates how to remove the price field from the results if the user making the request is in role NonMember. If you wish to remove another field please refer to the code snippet below and apply your custom logic.
If you are in a multilingual context fields have suffixes concatenated to them so the field you are looking for will have to reflect that specification e.g. price_es.
using System;
using System.Collections.Generic;
using System.Linq;
using Hawksearch.Api.Models;
using Hawksearch.SDK.Search;
using Telerik.Sitefinity.Security;
using Telerik.Sitefinity.Security.Claims;
using Telerik.Sitefinity.Security.Configuration;
namespace SitefinityWebApp.Search
{
public class CustomSearchApiModel : SearchApiModel
{
private const string PriceFieldName = "price";
public override AutocompleteResult GetAutocompleteSecuredResult(AutocompleteQuery query)
{
var autocompleteResults = base.GetAutocompleteSecuredResult(query);
var resultList = autocompleteResults.Products.Select(p => p.Results).ToList();
var userRoleIds = this.GetUserRoleIds();
if (this.CheckUserRoles(userRoleIds))
{
this.ModifyResults(resultList);
}
return autocompleteResults;
}
public override SearchResult GetSearchSecuredResult(SearchQuery query)
{
var searchResults = base.GetSearchSecuredResult(query);
var resultList = searchResults.Results;
var userRoleIds = this.GetUserRoleIds();
if (this.CheckUserRoles(userRoleIds))
{
this.ModifyResults(resultList);
}
return searchResults;
}
private bool CheckUserRoles(List<Guid> userRoleIds)
{
var roleManager = RoleManager.GetManager();
var role = roleManager.GetRoles().FirstOrDefault(r => r.Name == "NonMember");
if (role != null)
{
var nonMemberRoleId = role.Id;
if (userRoleIds.Contains(nonMemberRoleId))
{
return true;
}
}
return false;
}
private void ModifyResults(List<Result> results)
{
foreach (var item in results)
{
if (item.Document.ContainsKey(PriceFieldName))
{
item.Document.Remove(PriceFieldName);
}
}
}
private List<Guid> GetUserRoleIds()
{
var identity = ClaimsManager.GetCurrentIdentity();
var userRoleIds = new List<Guid>();
var currentUserId = identity.UserId;
var securityConfig = Telerik.Sitefinity.Configuration.Config.Get<SecurityConfig>();
if (currentUserId == Guid.Empty)
{
var everyoneId = securityConfig.ApplicationRoles.Values.First(r => r.Name == "Everyone").Id;
userRoleIds.Add(everyoneId);
}
else
{
var roleIds = identity.Roles.Select(r => r.Id).ToList();
roleIds.Add(SecurityManager.CurrentUserId);
userRoleIds.AddRange(roleIds);
}
return userRoleIds;
}
}
}
Register custom model
In order to use your custom model you need to register it in the Global.asax. Please refer to the following code snippet.
using System;
using Hawksearch.Api.Models;
using SitefinityWebApp.Search;
using Telerik.Sitefinity.Abstractions;
using Telerik.Sitefinity.Frontend;
namespace SitefinityWebApp
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
Bootstrapper.Bootstrapped += Bootstrapper_Bootstrapped;
}
private void Bootstrapper_Bootstrapped(object sender, EventArgs e)
{
var resolver = FrontendModule.Current.DependencyResolver;
resolver.Rebind<ISearchApiModel>().To<CustomSearchApiModel>();
}
}
}
Activate Filter by Permissions
In order for Filtering by permissions to work you need to activate it. If you need to more information please refer to the following article :
Filter by Permissions: Setup
Test custom filtering functionality
- Login as the newly create user
- Open the page with the Hawksearch widgets in the frontend
- Make a search request
- Check the response to see that the Price field is not returned.
Overriding the Popular searches component to add a custom label
Goal
The aim of this article is to demonstrate how to override the SearchSuggestion and PopularSearches component in order to customize the popular searches label.
This customization is available for versions x.xxxx.xx.33 and above of the connector.
Components override
In order to customize the popular searches label you need to first add that component to the SearchSuggestions component. Here on line 14 we add the needed component.
<script id="vue-hawksearch-search-suggestions" type="x-template">
<div class="hawk-autosuggest-menu">
<template v-if="fieldFocused && (loadingSuggestions || suggestions)">
<ul class="hawk-dropdown-menu hawk-autosuggest-menu__list hawk-autosuggest-outer-list">
<template v-if="loadingSuggestions">
<li class="hawk-autosuggest-menu__item">{{ $t('Loading') }}...</li>
</template>
<template v-else-if="suggestions.Products.length">
<ul class="hawk-autosuggest-inner-list">
<h3>Results</h3>
<suggestion-item v-for="item in suggestions.Products" :item="item" :key="item.Results.DocId" v-on:itemselected="onItemSeleted"></suggestion-item>
</ul>
<div class="autosuggest-inner-container" v-if="suggestions.Categories.length || suggestions.Popular.length || suggestions.Content.length">
<popular-container :suggestions="suggestions"></popular-container>
</div>
</template>
<template v-else>
<li class="hawk-autosuggest-menu__item">{{ $t('No Results') }}</li>
</template>
</ul>
</template>
</div>
</script>
After the component is in use we can now override its template to display a custom label. Here on line 3, we add the custom label
<script id="vue-hawksearch-popular-container" type="x-template">
<div v-if="suggestions && suggestions.Popular && suggestions.Popular.length">
<h3>Popular searches</h3>
<li v-for="popular in suggestions.Popular" :key="popular.Value" class="autosuggest-menu__item">
<div v-html="popular.Value"></div>
</li>
</div>
</script>
Ready-to-use example
The code snippet below demonstrates a fully functional HawksearchBox widget with the component override included in it.
@model HawksearchWidgets.Mvc.ViewModels.HawksearchBox.SearchBoxViewModel
@using Telerik.Sitefinity.Frontend.Mvc.Helpers;
@using Telerik.Sitefinity.Modules.Pages
@using Telerik.Sitefinity.Services
@using Newtonsoft.Json
@if (Model.Version == "V2L" || Model.Version == "V3L" || Model.Version == "V4L")
{
<div class="site-search">
<input type="hidden" value="@Model.Index">
<input type="hidden" value="@Model.ResultsUrl" data-search-page="@Model.ResultsUrl">
<div>
<input class="site-search-input" placeholder="@Html.HtmlSanitize(Html.Resource("ImLookingFor", "HawkWidgetsResources"))" type="text" id="txtSiteSearch">
<button class="site-search-btn" id="btnSiteSearch">
<span class="visually-hidden">@Html.HtmlSanitize(Html.Resource("SubmitButtonText"))</span>
<svg class="icon icon-search-01">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-search-01"></use>
</svg>
</button>
</div>
</div>
//string hawkCssUrl = Hawksearch.Helpers.HawksearchApiHelper.GetHawksearchUrl().Replace("http://", "https://") + "/includes/hawksearch.css";
@*@Html.StyleSheet(hawkCssUrl, "head", false)*@
@Html.Script(ScriptRef.JQuery, "top", false)
if (!SystemManager.IsDesignMode)
{
@Html.Script(Url.WidgetContent("Mvc/Scripts/polyfills.js"), "top", false)
@Html.Script(Url.WidgetContent("Mvc/Scripts/hawksearch-init.js"), "head", false)
@Html.Script(Url.WidgetContent("Mvc/Scripts/hawksearch.js"), "head", false)
@Html.Script(Url.WidgetContent("Mvc/Scripts/hawksearch-autosuggest.js"), "bottom", false)
@Html.Script(Url.WidgetContent("Mvc/Scripts/hawksearchbox.js"), "bottom", false)
}
}
else
{
@Html.Script(Url.WidgetContent("assets/build/js/vendor.bundle.js"), "bottom", false)
@Html.Script(Url.WidgetContent("assets/build/js/main.js"), "bottom", false)
@Html.StyleSheet(Url.WidgetContent("assets/build/css/vendor.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/build/css/main.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/vue-hawksearch.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/vue-hawksearch-override.css"), "head")
<div data-component="vue-app-searchbox"
data-client-guid="@Model.ClientId"
data-hawksearch-tracking-api="@Model.TrackingUrl"
data-hawksearch-base-api="@Model.HawksearchBaseAPI"
data-hawksearch-search-api="@Model.HawksearchSearchingAPI"
data-hawksearch-autocomplete-api="@Model.AutocompleteUrl"
data-search-page="@Model.ResultsUrl"
data-show-searchbox="True"
data-json-params="@Model.Data"
data-index-name="@Model.Index"
data-current-culture="@Model.CurrentCulture">
<div class="hawk">
<div class="hawk__header">
<div data-component="hawksearch-field">
<search-box search-page="@Model.ResultsUrl"></search-box>
</div>
</div>
</div>
</div>
<script id="vue-hawksearch-search-suggestions" type="x-template">
<div class="hawk-autosuggest-menu">
<template v-if="fieldFocused && (loadingSuggestions || suggestions)">
<ul class="hawk-dropdown-menu hawk-autosuggest-menu__list hawk-autosuggest-outer-list">
<template v-if="loadingSuggestions">
<li class="hawk-autosuggest-menu__item">{{ $t('Loading') }}...</li>
</template>
<template v-else-if="suggestions.Products.length">
<ul class="hawk-autosuggest-inner-list">
<h3>Results</h3>
<suggestion-item v-for="item in suggestions.Products" :item="item" :key="item.Results.DocId" v-on:itemselected="onItemSeleted"></suggestion-item>
</ul>
<div class="autosuggest-inner-container" v-if="suggestions.Categories.length || suggestions.Popular.length || suggestions.Content.length">
<popular-container :suggestions="suggestions"></popular-container>
</div>
</template>
<template v-else>
<li class="hawk-autosuggest-menu__item">{{ $t('No Results') }}</li>
</template>
</ul>
</template>
</div>
</script>
<script id="vue-hawksearch-popular-container" type="x-template">
<div v-if="suggestions && suggestions.Popular && suggestions.Popular.length">
<h3>Popular searches</h3>
<li v-for="popular in suggestions.Popular" :key="popular.Value" class="autosuggest-menu__item">
<div v-html="popular.Value"></div>
</li>
</div>
</script>
<script data-translations="vue-translations" type="application/json">
{
"Narrow Results": "@Html.HtmlSanitize(Html.Resource("NarrowResults", "HawkWidgetsResources"))",
"Search Results": "@Html.HtmlSanitize(Html.Resource("SearchResults", "HawkWidgetsResources"))",
"Search Results for": "@Html.HtmlSanitize(Html.Resource("SearchResultsFor", "HawkWidgetsResources"))",
"Sort By": "@Html.HtmlSanitize(Html.Resource("SortBy", "HawkWidgetsResources"))",
"Enter a search term": "@Html.HtmlSanitize(Html.Resource("EnterKeyword", "HawkWidgetsResources"))",
"Quick Lookup": "@Html.HtmlSanitize(Html.Resource("QuickLookup", "HawkWidgetsResources"))",
"Clear All": "@Html.HtmlSanitize(Html.Resource("ClearAll", "HawkWidgetsResources"))",
"Clear": "@Html.HtmlSanitize(Html.Resource("Clear", "HawkWidgetsResources"))",
"No Results": "@Html.HtmlSanitize(Html.Resource("NoResults", "HawkWidgetsResources"))",
"Loading": "@Html.HtmlSanitize(Html.Resource("Loading", "HawkWidgetsResources"))",
"You've Selected": "@Html.HtmlSanitize(Html.Resource("YouSelected", "HawkWidgetsResources"))",
"response_error_generic": "An error occurred while searching for your results. Please contact the site administrator."
}
</script>
}
React Examples
Resolve search term
Goal
Expose the entered search term on the results page.
Steps to resolve
- Create/Open the components folder in the designated react directory (e.g. hawksearch/react/components).
- Create new component file for the search term (e.g. SearchTerm.tsx) and open for editing. Use the following sample as a base for the component:
import React, { useEffect, useState } from 'react';
import { useHawksearch } from 'react-hawksearch';
function SearchTerm() {
const {
store: { searchResults },
} = useHawksearch();
const [keyword, setKeyword] = useState('');
useEffect(() => {
if (searchResults) {
setKeyword(decodeURIComponent(searchResults.Keyword || ''));
}
}, [searchResults]);
return (
<div className="custom-class-name">
<h1>{keyword}</h1>
</div>
);
}
export default SearchTerm;
- Open the results widget js file for editing and import the newly created component
...
import SearchTerm from '../components/SearchTerm';
...
- Place the component in the desired place
function App() {
return (
<Hawksearch config={hawkConfig}>
<QueryStringListener />
<div className="hawk">
<div className="hawk__header">
<SearchBox />
</div>
<div className="custom-wrapper-class">
<SearchTerm />
</div>
<div className="hawk__body">
<FacetRail />
<Results />
</div>
</div>
</HawkSearch>
);
}
- Save, build the react bundle and re-build the solution.
Updated almost 2 years ago