Connector Configuration
Add related data to the Hawksearch index
Goal
This article provides information on how to push related data into a document for indexing.
Setup related data
- Open the backend of your Sitefinity instance
- Open the content → news page (your-site-domain/Sitefinity/adminapp/content/newsitems)
- Click the gears icon (settings) in the upper right corner
- Open the custom fields page and add a new field
- Press Continue, then Done and save the changes
- Open Content Types → Events and create an event
- Add the newly created event to a news item
Field Setup
To create the related data field in Hawksearch, you must first add it to the 'Additional fields for indexing' section found within the 'Search Index Properties' settings for the index in the Sitefinity Administration. This step is essential for the field to be recognized and utilized within the Hawksearch system.
Setup search service
In order to add the related data to the index you need to create a custom search service which inherits the HawkseachService class and overrides the CreateIndex and UpdateIndex methods. Please refer to the code snippet below.
Note
The following snippets demonstrates how to index an news article which has a custom field related data - event. If you wish to index other related data please adjust the snippet to your needs.
using System;
using System.Collections.Generic;
using System.Linq;
using Hawksearch.Search;
using Telerik.Sitefinity.Services.Search.Data;
using Telerik.Sitefinity.Data.ContentLinks;
using Telerik.Sitefinity.Modules.Events;
using Telerik.Sitefinity.Services.Search.Model;
using Telerik.Sitefinity.Services.Search.Publishing;
using Telerik.Sitefinity.Services.Search;
namespace SitefinityWebApp
{
public class CustomSearchService : HawksearchService
{
private const string NewsItemContentType = "Telerik.Sitefinity.News.Model.NewsItem";
public override void CreateIndex(string sitefinityIndexName, IEnumerable<IFieldDefinition> fieldDefinitions)
{
var definitions = fieldDefinitions.ToList();
var parentTitleFieldDefinition = Telerik.Sitefinity.Abstractions.ObjectFactory.Resolve<IFieldDefinition>();
parentTitleFieldDefinition.Name = "event";
parentTitleFieldDefinition.Type = typeof(string);
definitions.Add(parentTitleFieldDefinition);
base.CreateIndex(sitefinityIndexName, definitions);
}
public override void UpdateIndex(string name, IEnumerable<IDocument> documents)
{
var documentList = new List<IDocument>(documents);
var document = documents.FirstOrDefault();
if (document != null)
{
var contentTypeField = document.Fields.FirstOrDefault(f => f.Name == "ContentType");
if (contentTypeField != null)
{
var contentType = contentTypeField.Value.ToString();
if (string.Equals(contentType, NewsItemContentType, StringComparison.InvariantCultureIgnoreCase))
{
var eventsManager = EventsManager.GetManager();
var contentLinksManager = ContentLinksManager.GetManager();
var newsItemsContentLinks = contentLinksManager.GetContentLinks().Where(cl => cl.ParentItemType == NewsItemContentType).ToList();
var eventIds = newsItemsContentLinks.Select(cl => cl.ChildItemId).ToList();
var events = eventsManager.GetEvents().Where(e => eventIds.Contains(e.Id));
documentList = new List<IDocument>();
foreach (var doc in documents)
{
var fields = new List<IField>();
fields.AddRange(doc.Fields);
var documentIdField = doc.Fields.FirstOrDefault(f => f.Name == "OriginalItemId");
if (documentIdField != null)
{
var id = Guid.Parse(documentIdField.Value.ToString());
var newsArticleContentLink = newsItemsContentLinks.FirstOrDefault(n => n.ParentItemId == id);
if (newsArticleContentLink != null)
{
var relatedEvent = events.FirstOrDefault(e => e.Id == newsArticleContentLink.ChildItemId);
if (relatedEvent != null)
{
var relatedEventField = new Field
{
Name = "event",
Value = relatedEvent.Title
};
fields.Add(relatedEventField);
}
}
}
var modifiedDocument = new Document(fields, document.IdentityField.Name);
documentList.Add(modifiedDocument);
}
}
}
base.UpdateIndex(name, documentList);
}
}
}
}
Once you implement the code in Visual Studio, build your solution and you will also have to reindex the index you are using from Administrator → Search Indexes → Action → Reindex
Expected results
Now if you Inspect your frontend page you should be able to find the related event field you have added to your document in the XHR search → results → document fields
Register Custom Search Service
In order to use your custom search service instead of the built-in one you need to register it in the backend.
Binding contenttypename field to custom values
Goal
This article includes information on how to bind the contenttypename field to custom values depending on the content type of the document.
Steps to bind
In order to add the custom value to contenttypename field you need to extend the HawksearchService and override the AdaptDocuments method.
This sample demonstrates how to bind custom values for News items and a dynamic module called MemberStories. If you wish to customize the names of other content types you need to extend the logic behind
GetContentTypeName
method.
using System.Collections.Generic;
using System.Linq;
using Hawksearch.SDK.Indexing;
using Hawksearch.Search;
using Telerik.Sitefinity.Services.Search.Data;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
protected override List<SubmitDocument> AdaptDocuments(IEnumerable<IDocument> documents)
{
var documentsList = base.AdaptDocuments(documents);
var document = documentsList.FirstOrDefault();
if (document != null)
{
var contentTypeField = document.Fields.FirstOrDefault(f => f.Name == "ContentType");
if (contentTypeField != null)
{
foreach (var doc in documentsList)
{
var contentTypeNameField = doc.Fields.FirstOrDefault(f => f.Name == "contenttypename");
if (contentTypeNameField != null)
{
var contentTypeFieldValue = contentTypeField.Values.FirstOrDefault();
if (contentTypeFieldValue != null)
{
var customContentTypeName = this.GetContentTypeName(contentTypeFieldValue);
if (!string.IsNullOrWhiteSpace(customContentTypeName))
{
contentTypeNameField.Values = new List<string>() { customContentTypeName };
}
}
}
}
}
}
return documentsList;
}
private string GetContentTypeName(string type)
{
var customContentTypeName = string.Empty;
if (type != null)
{
switch (type)
{
case "Telerik.Sitefinity.News.Model.NewsItem":
customContentTypeName = "News article";
break;
case "Telerik.Sitefinity.DynamicTypes.Model.MemberStories.MemberStories":
customContentTypeName = "Member stories";
break;
}
}
return customContentTypeName;
}
}
}
Once you implement the code in Visual Studio , build your solution and you will also have to reindex the index you are using from Administrator → Search Indexes → Action → Reindex
Expected results
Now if you Inspect your frontend page you should be able to find the contenttypename field you have changed to your document in the XHR search → results → document fields
Pushing additional information to a document
Goal
This article provides information on how to push additional information from a parent type to the document created for the child type when indexing.
Create parent-child relation
For the purpose of this article we are going to create two dynamic content types : Festivals
which is the parent type and Festival sessions
which is the child type. The types have the following fields:
Festival:
- Title - Short text
- Description - Short text
- Venue - Address
Festival Session:
- Title - Short text
- StartDate - Date
- EndDate - Date
Use this .zip with the extracted dynamic types and import them in your Sitefinity instance
You can import the file from Administration → Import/Export menu
Festivals-FestivalSessions.zip
Setup Additional fields in Index
The additional fields can be added in two ways:
- In the advanced section of the index
- Programmatically
In the advanced section
- Open Search indexes in the backend (your-site-domain//Sitefinity/Administration/Search)
- Open the index and add the additional fields in the Advanced section
- Save the changes
- You still need to programmatically Update the index. Implement the section
Add values to additional fields from bellow
.
Setup search service
In order to add additional fields programmatically to the index you need to create a custom search service which inherits the HawkseachService class and overrides the CreateIndex and UpdateIndex methods. Please refer to the code snippet below.
Programmatically
using System.Collections.Generic;
using System.Linq;
using Hawksearch.Search;
using Telerik.Sitefinity.Services.Search;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
private const string ParentTitleFieldName = "ParentTitle";
private const string ParentDescriptionFieldName = "ParentDescription";
private const string ParentVenueFieldName = "ParentVenue";
public override void CreateIndex(string sitefinityIndexName, IEnumerable<IFieldDefinition> fieldDefinitions)
{
var definitions = fieldDefinitions.ToList();
var parentTitleFieldDefinition = Telerik.Sitefinity.Abstractions.ObjectFactory.Resolve<IFieldDefinition>();
parentTitleFieldDefinition.Name =ParentTitleFieldName;
parentTitleFieldDefinition.Type = typeof(string);
definitions.Add(parentTitleFieldDefinition);
var parentDescriptionFieldDefinition = Telerik.Sitefinity.Abstractions.ObjectFactory.Resolve<IFieldDefinition>();
parentDescriptionFieldDefinition.Name = ParentDescriptionFieldName;
parentDescriptionFieldDefinition.Type = typeof(string);
definitions.Add(parentDescriptionFieldDefinition);
var parentVenueFieldDefinition = Telerik.Sitefinity.Abstractions.ObjectFactory.Resolve<IFieldDefinition>();
parentVenueFieldDefinition.Name = ParentVenueFieldName;
parentVenueFieldDefinition.Type = typeof(string);
definitions.Add(parentVenueFieldDefinition);
base.CreateIndex(sitefinityIndexName, definitions);
}
}
}
Add values to additional fields
Setup search service
In order to add additional fields programmatically to the index you need to create a custom search service which inherits the HawkseachService class and overrides the UpdateIndex methods. Please refer to the code snippet below.
If you chose to add the fields programmatically just add the namespaces and the method from the code snippet to your custom search service.
In the UpdateIndex
when we find batch of documents to which we would like to add fields (in our case it’s Festival Session), we simply iterate through them, find both the child and parent items with the help of the manager and then we add the values to the corresponding fields in the document.
using System;
using System.Collections.Generic;
using System.Linq;
using Hawksearch.Search;
using Telerik.Sitefinity.DynamicModules;
using Telerik.Sitefinity.GenericContent.Model;
using Telerik.Sitefinity.GeoLocations.Model;
using Telerik.Sitefinity.Model;
using Telerik.Sitefinity.Publishing;
using Telerik.Sitefinity.Services.Search.Data;
using Telerik.Sitefinity.Services.Search.Model;
using Telerik.Sitefinity.Services.Search.Publishing;
using Telerik.Sitefinity.Utilities.TypeConverters;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
private const string ParentTitleFieldName = "ParentTitle";
private const string ParentDescriptionFieldName = "ParentDescription";
private const string ParentVenueFieldName = "ParentVenue";
private const string ChildItemType = "Telerik.Sitefinity.DynamicTypes.Model.Festivals.FestivalSession";
public override void UpdateIndex(string name, IEnumerable<IDocument> documents)
{
var type = string.Empty;
var contentType = documents.FirstOrDefault().Fields.FirstOrDefault(f => f.Name == "ContentType");
var documentList = new List<IDocument>(documents);
if (contentType != null)
{
type = contentType.Value.ToString();
}
if (string.Equals(type, ChildItemType, StringComparison.InvariantCultureIgnoreCase))
{
var dynamicModuleManager = DynamicModuleManager.GetManager();
var childType = TypeResolutionService.ResolveType(ChildItemType);
var documentsIds = documents.Select(d => d.Fields.FirstOrDefault(f => f.Name == "Id")?.Value.ToString()).ToList();
var children = dynamicModuleManager.GetDataItems(childType).Where(i => i.Status == ContentLifecycleStatus.Live && documentsIds.Contains(i.Id.ToString())).ToList();
documentList = new List<IDocument>();
foreach (var document in documents)
{
var fields = new List<IField>();
fields.AddRange(document.Fields);
var id = document.Fields.FirstOrDefault(f => f.Name == "Id");
if (id != null)
{
var documentId = Guid.Empty;
if (Guid.TryParse(id.Value.ToString(), out documentId))
{
var child = children.FirstOrDefault(i => i.Id == documentId);
if (child != null)
{
var parent = child.SystemParentItem;
if (parent != null)
{
if (parent.GetPropertyValue<Lstring>("Title") != null)
{
var parentTitleField = new Field
{
Name = ParentTitleFieldName,
Value = parent.GetPropertyValue<Lstring>("Title").Value
};
fields.Add(parentTitleField);
}
if (parent.GetPropertyValue<Lstring>("Description") != null)
{
var parentDescriptionField = new Field
{
Name = ParentDescriptionFieldName,
Value = parent.GetPropertyValue<Lstring>("Description").Value
};
fields.Add(parentDescriptionField);
}
if (parent.GetPropertyValue<Address>("Venue") != null)
{
var parentVenueField = new Field
{
Name = ParentVenueFieldName,
Value = parent.GetPropertyValue<Address>("Venue").City
};
fields.Add(parentVenueField);
}
}
}
}
var modifiedDocument = new Document(fields, document.IdentityField.Name);
documentList.Add(modifiedDocument);
}
}
}
base.UpdateIndex(name, documentList);
}
}
}
Once you implement the code in Visual Studio , build your solution and you will also have to reindex the index you are using from Administrator → Search Indexes → Action → Reindex
Expected results
Now if you Inspect your frontend page you should be able to find the Festival information under the child FestivalSession fields that is being added to your document in the XHR search → results → document fields
Exclude documents from being indexed based on their parent library
Goal
This article contains information on how to exclude documents from being indexed based on their parent library.
Create document libraries
- Open the backend of your Sitefinity instance
- Navigate to Content → Documents & Files (your-site-domain/Sitefinity/Content/Documents)
- Click on Create a library
- Create two libraries :
- The first one should be named “Careers“
- The second one should be named “Research“
- Add documents to both of the libraries.
- Navigate to Administration → Search indexes (your-site-domain/Sitefinity/Administration/Search indexes)
- Open your currently used and active index
- Select Documents from the scope. This will add both documents from Careers and Research to the index
For the purpose of this example we are going to exclude the “Careers“ library from being indexed, by implementing the code snippet from below
Exclude library
Setup search service
In order to plug in and modify the documents that are being indexed you need to create a custom search service which inherits the HawkseachService class and overrides the UpdateIndex methods. Please refer to the code snippet below.
using System;
using System.Collections.Generic;
using System.Linq;
using Hawksearch.Search;
using Telerik.Sitefinity.GenericContent.Model;
using Telerik.Sitefinity.Services.Search.Data;
using Telerik.Sitefinity.Modules.Libraries;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
private const string DocumentContentType = "Telerik.Sitefinity.Libraries.Model.Document";
private const string LibraryName = "Careers";
public override void UpdateIndex(string name, IEnumerable<IDocument> documents)
{
var type = string.Empty;
var document = documents.FirstOrDefault();
var modifiedDocuments = new List<IDocument>(documents);
if (document != null)
{
var contentTypeField = document.Fields.FirstOrDefault(f => f.Name == "ContentType");
if (contentTypeField != null)
{
type = contentTypeField.Value.ToString();
}
}
if (string.Equals(type, DocumentContentType, StringComparison.InvariantCultureIgnoreCase))
{
var librariesManager = LibrariesManager.GetManager();
var documentLibrary = librariesManager.GetDocumentLibraries().Where(l => l.Title == LibraryName).FirstOrDefault();
if (documentLibrary != null)
{
foreach (var doc in documents)
{
var idField = doc.Fields.FirstOrDefault(f => f.Name == "Id");
var documentId = Guid.Empty;
if (idField != null)
{
if (Guid.TryParse(idField.Value.ToString(), out documentId))
{
if (documentId != Guid.Empty)
{
var matchingDocument = documentLibrary.ChildContentItems.FirstOrDefault(i => i.Id == documentId && i.Status == ContentLifecycleStatus.Live);
if (matchingDocument != null)
{
modifiedDocuments.Remove(doc);
}
}
}
}
}
}
}
base.UpdateIndex(name, modifiedDocuments);
}
}
}
Once you implement the code in Visual Studio , build your solution and you will also have to reindex the index you are using from Administrator → Search Indexes → Action → Reindex
Expected results
Now if you Inspect your frontend page you should neither be able to see nor find the Careers document within your results in the XHR search → results → document fields
Index documents above the document size limit
Goal
This article provides information on how to index documents which are above the document size limit.
Setup document size limit
- Open the backend of your Sitefinity instance
- Navigate to Administartion → Settings and click Advanced (your-site-domain/Sitefinity/Administration/Settings/Advanced)
- Open the Hawksearch configuration
- Under document size limit enter 100KB
- Save the changes
Upload content above the limit
For the purpose of this example we are going to upload .docx, .txt and .pdf files.
- Open the backend of your Sitefinity instance
- Navigate to Content → Documents & Files
- Upload files above the document size limit e.g. 20MB
- Navigate to Administration → Search indexes (your-site-domain/Sitefinity/Administration/Search indexes)
- Open your currently used and active Index
- Select Documents from the scope. This will add the documents you have uploaded to the index
During indexing the files are stripped and only the text content is extracted. Some files contain a lot of metadata or embedded resources (e.g. photos) so a 20MB .pdf may only contain 2MB of actual data.
Setup search service
In order to index documents above the document size limit you need to inherit the HawksearchService class and override the AdaptDocuments.
Here we will demonstrate how to:
empty the content field
ORtake the first 500 words in it
.
The following code snippet demonstrates how to strip the document from it’s Content field in order to pass the document size limit check.
Empty content field
using System;
using System.Collections.Generic;
using System.Linq;
using Hawksearch.Search;
using Telerik.Sitefinity.Services.Search.Data;
using Telerik.Sitefinity.Configuration;
using Telerik.Sitefinity.Services.Search.Model;
using Hawksearch.Configuration;
using Hawksearch.SDK.Indexing;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
private const string DocumentContentType = "Telerik.Sitefinity.Libraries.Model.Document";
protected override List<SubmitDocument> AdaptDocuments(IEnumerable<IDocument> documents)
{
var doc = documents.ToList().FirstOrDefault();
var documentList = new List<IDocument>(documents);
if (doc != null)
{
var contentTypeField = doc.Fields.FirstOrDefault(f => f.Name == "ContentType");
if (contentTypeField != null)
{
if (string.Equals(contentTypeField.Value.ToString(), DocumentContentType, StringComparison.InvariantCultureIgnoreCase))
{
var configManager = ConfigManager.GetManager();
var hawkConfig = configManager.GetSection<HawkSearchConfig>();
documentList.Clear();
foreach (var document in documents)
{
var modifiedDocument = document;
var documentSize = this.CalculateDocumentSize(document);
if (documentSize > hawkConfig.DocumentSizeLimit)
{
modifiedDocument = this.ModifyDocument(document);
}
documentList.Add(modifiedDocument);
}
}
}
}
return base.AdaptDocuments(documentList);
}
private double CalculateDocumentSize(IDocument document)
{
var documentSize = 0.0;
foreach (var field in document.Fields)
{
if (field.Value != null)
{
documentSize += System.Text.Encoding.Unicode.GetByteCount(field.Value.ToString()) / 1024.0;
}
}
return documentSize;
}
private IDocument ModifyDocument(IDocument document)
{
var fields = new List<IField>(document.Fields);
var contentField = document.Fields.FirstOrDefault(f => f.Name == "Content");
if (contentField != null)
{
contentField.Value = string.Empty;
}
var modifiedDocument = new Document(fields, document.IdentityField.Name);
return modifiedDocument;
}
}
}
Once you implement the code in Visual Studio , build your solution and you will also have to reindex the index you are using from Administrator → Search Indexes → Action → Reindex
Expected results
Now if you Inspect your frontend page you should be able to see the title of your large document, but not the content. There will be no content field as well in the XHR search → results → document fields
Take the first 500 words
using System;
using System.Collections.Generic;
using System.Linq;
using Hawksearch.Search;
using Telerik.Sitefinity.Services.Search.Data;
using Telerik.Sitefinity.Configuration;
using Telerik.Sitefinity.Services.Search.Model;
using Hawksearch.Configuration;
using Hawksearch.SDK.Indexing;
using Field = Telerik.Sitefinity.Services.Search.Publishing.Field;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
private const string DocumentContentType = "Telerik.Sitefinity.Libraries.Model.Document";
protected override List<SubmitDocument> AdaptDocuments(IEnumerable<IDocument> documents)
{
var doc = documents.ToList().FirstOrDefault();
var documentList = new List<IDocument>(documents);
if (doc != null)
{
var contentTypeField = doc.Fields.FirstOrDefault(f => f.Name == "ContentType");
if (contentTypeField != null)
{
if (string.Equals(contentTypeField.Value.ToString(), DocumentContentType, StringComparison.InvariantCultureIgnoreCase))
{
var configManager = ConfigManager.GetManager();
var hawkConfig = configManager.GetSection<HawkSearchConfig>();
documentList.Clear();
foreach (var document in documents)
{
var modifiedDocument = document;
var documentSize = this.CalculateDocumentSize(document);
if (documentSize > hawkConfig.DocumentSizeLimit)
{
modifiedDocument = this.ModifyDocument(document);
}
documentList.Add(modifiedDocument);
}
}
}
}
return base.AdaptDocuments(documentList);
}
private double CalculateDocumentSize(IDocument document)
{
var documentSize = 0.0;
foreach (var field in document.Fields)
{
if (field.Value != null)
{
documentSize += System.Text.Encoding.Unicode.GetByteCount(field.Value.ToString()) / 1024.0;
}
}
return documentSize;
}
private IDocument ModifyDocument(IDocument document)
{
var wordLimit = 500;
var fields = new List<IField>(document.Fields);
var contentField = document.Fields.FirstOrDefault(f => f.Name == "Content");
fields.Remove(contentField);
contentField = this.ExtractFieldContent(contentField, wordLimit);
fields.Add(contentField);
var modifiedDocument = new Document(fields, document.IdentityField.Name);
return modifiedDocument;
}
private IField ExtractFieldContent(IField contentField, int wordLimit)
{
var fieldValue = contentField.Value.ToString();
if (!string.IsNullOrWhiteSpace(fieldValue))
{
var modifiedContent = string.Join(" ", fieldValue.Split(' ').Take(wordLimit).ToArray());
contentField = new Field
{
Name = "Content",
Value = modifiedContent
};
}
return contentField;
}
}
}
Once you implement the code in Visual Studio , build your solution and you will also have to reindex the index you are using from Administrator → Search Indexes → Action → Reindex
Expected results
Now if you Inspect your frontend page you should be able to see the content with first 500 symbols of your large document. There will be also a content field with 500 symbols in the XHR search → results → document fields
Register Custom Search Service
In order to use your custom search service instead of the built-in one you need to register it in the backend.
Remove deleted content from indexed pages
Goal
The purpose of this article is to demonstrate how to remove deleted content from already indexed pages.
Overview
The idea behind this article is an edge case in which if you have placed e.g. a news widget on a page and you have indexed that page. On the frontend page all of the news will be visible and we would be able to search them within the current page results as they are a part of its content. If you then delete one of the news articles you will continue to see information about it when you type in the search bar of the page. The following sample fixes this issue as it republishes the page and updates it in the index.
The issue has been reported to Progress, but still has no official resolution
Setup query field
In order for the following sample to work correctly you need to have made the IdentityField queryable. To do so follow these steps:
- Open the dashboard of you Hawksearch instance.
- Navigate to the Workbench
- Under Data Configuration select Fields
- Locate IdentityField and open it for edit
- In the Advanced options check this:
Create custom search service
To remove the deleted content from already indexed pages you need to create a custom search service class and override the RemoveDocuments method. Please refer to the code snippet below.
using System;
using System.Collections.Generic;
using System.Linq;
using Hawksearch.Data;
using Hawksearch.SDK.Search;
using Hawksearch.Search;
using Telerik.Sitefinity.Data;
using Telerik.Sitefinity.Modules.Pages;
using Telerik.Sitefinity.Pages.Model;
using Telerik.Sitefinity.Services;
using Telerik.Sitefinity.Services.Search.Data;
using Telerik.Sitefinity.Utilities.TypeConverters;
using Telerik.Sitefinity.Workflow;
using SearchQuery = Hawksearch.SDK.Search.SearchQuery;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
public override void RemoveDocuments(string indexName, IEnumerable<IDocument> documents)
{
var client = this.InitializeClient();
var manager = HawkManager.GetManager();
var indexMapping = manager.GetIndexMappings().FirstOrDefault(i => i.SitefinityIndex == indexName);
foreach (var item in documents)
{
var id = item.IdentityField.Value.ToString();
var searchQuery = new SearchQuery();
if (indexMapping != null)
{
searchQuery.IndexName = indexMapping.HawkIndex;
}
searchQuery.Keyword = id;
var results = client.Search(searchQuery);
var result = results.Results.FirstOrDefault();
if (result != null)
{
var pageIds = this.GetPageIds(result);
foreach (var pageId in pageIds)
{
this.RepublishPage(pageId);
}
}
}
base.RemoveDocuments(indexName, documents);
}
private void RepublishPage(Guid pageId)
{
var manager = PageManager.GetManager();
var page = manager.GetPageNode(pageId);
var bag = new Dictionary<string, string>();
bag.Add("ContentType", typeof(PageNode).FullName);
WorkflowManager.MessageWorkflow(page.Id, typeof(PageNode), null, "Publish", false, bag);
}
private List<Guid> GetPageIds(Result result)
{
var resultPageIds = new List<Guid>();
var locationsService = SystemManager.GetContentLocationService();
if (result.Document.ContainsKey("contenttype"))
{
var contentType = result.Document["contenttype"].FirstOrDefault();
if (contentType != null)
{
var type = TypeResolutionService.ResolveType(contentType, false);
if (type != null)
{
var mappedManager = ManagerBase.GetMappedManager(type, "");
if (result.Document.ContainsKey("originalitemid"))
{
var itemId = result.Document["originalitemid"].FirstOrDefault();
if (itemId != null)
{
var pageIds = locationsService.GetItemLocations(type, mappedManager.Provider.Name, Guid.Parse(itemId)).Select(il => il.PageId).ToList();
resultPageIds.AddRange(pageIds);
}
}
}
}
}
return resultPageIds;
}
}
}
Once you implement the code in Visual Studio , build your solution and you will also have to reindex the index you are using from Administrator → Search Indexes → Action → Reindex
Expected results
Now if you Delete a News item from the Sitefinity Backend UI and then search for it in page search bar, it should not find any results
How to index DateTime fields
Goal
The aim of this article is to demonstrate how to index DateTime fields so that their type is properly parsed passed to the Hawksearch API.
Overview
This documentation explains how to index fields of type DateTime in Hawksearch. The Hawksearch DateTime format differs from Sitefinity's default DateTime format, so when the field is created in Hawksearch it’s type is String. The solution below explains how to alter the DateTime type so that it is properly mapped in Hawksearch.
Add fields for indexing
In order for these fields to be indexed you first need to add them to index.
- Navigate to Administration → Search indexes (your-site-domain/Sitefinity/Administration/Search)
- Open the index for editing
- Under Advanced → Additional fields for indexing add the fields e.g. PublishedDate, ReleasedDate
- Save the changes
Add the fields to a Content item
In order for these fields to be indexed you also need to add them as a custom fields to a e.g. News item
- Create a News item
- On the top right side click on the Settings icon and select the Custom Fields
- Add a custom field of type Date and Time and with the name you have added in the Advanced settings of the Index e.g. PublishedDate
- Press Continue and Done
- Save Changes
Create configuration
In order to alter the type of particular fields we need to store them in a configuration and then use that configuration in a custom search service. Below you will find the ready to use configuration:
using System.Configuration;
using Telerik.Sitefinity.Configuration;
using Telerik.Sitefinity.Localization;
namespace SitefinityWebApp
{
[ObjectInfo(Title = "Datetime fields Title", Description = "Datetime fields Description")]
public class DatetimeFieldsConfig : ConfigSection
{
[ConfigurationProperty("DatetimeFields")]
public ConfigElementDictionary<string, DatetimeField> DatetimeFields
{
get { return (ConfigElementDictionary<string, DatetimeField>)this["DatetimeFields"]; }
}
}
public class DatetimeField : ConfigElement
{
public DatetimeField(ConfigElement parent) : base(parent)
{
}
[ConfigurationProperty("Name", IsRequired = true, IsKey = true)]
[ObjectInfo(Title = "Name", Name = "Name", Description = "Field name.")]
public string Name
{
get { return (string)this["Name"]; }
set { this["Name"] = value; }
}
}
}
Register configuration
In order for your configuration to appear in the Advanced settings of your Sitefinity instance you need to register it at application start in your Global.asax file. Please refer to the code below:
using System.Configuration;
using Telerik.Sitefinity.Configuration;
using Telerik.Sitefinity.Localization;
namespace SitefinityWebApp
{
[ObjectInfo(Title = "Datetime fields Title", Description = "Datetime fields Description")]
public class DatetimeFieldsConfig : ConfigSection
{
[ConfigurationProperty("DatetimeFields")]
public ConfigElementDictionary<string, DatetimeField> DatetimeFields
{
get { return (ConfigElementDictionary<string, DatetimeField>)this["DatetimeFields"]; }
}
}
public class DatetimeField : ConfigElement
{
public DatetimeField(ConfigElement parent) : base(parent)
{
}
[ConfigurationProperty("Name", IsRequired = true, IsKey = true)]
[ObjectInfo(Title = "Name", Name = "Name", Description = "Field name.")]
public string Name
{
get { return (string)this["Name"]; }
set { this["Name"] = value; }
}
}
}
Add your custom fields to Sitefinity Settings
After you have registered the configuration :
- Navigate to Administration → Settings → Advanced → DateTimeFields (your-site-domain/Sitefinity/Administration/Settings/Advanced)
- Add the DateTime fields you wish to alter e.g. PublishedDate
Setup search service
In order to modify all the DateTime fields to the index you need to create a custom search service which inherits the HawkseachService class and overrides the CreateIndex , which also implement the ModifyDatetimeFields method . Please refer to the code snippet below.
using System;
using System.Collections.Generic;
using System.Linq;
using Hawksearch.Search;
using Telerik.Sitefinity.Configuration;
using Telerik.Sitefinity.Services.Search;
namespace SitefinityWebApp
{
public class CustomSearchService : HawksearchService
{
public override void CreateIndex(string sitefinityIndexName, IEnumerable<IFieldDefinition> fieldDefinitions)
{
var definitionList = new List<IFieldDefinition>(fieldDefinitions);
this.ModifyDatetimeFields(definitionList);
base.CreateIndex(sitefinityIndexName, definitionList);
}
private void ModifyDatetimeFields(List<IFieldDefinition> definitionList)
{
var configManager = ConfigManager.GetManager();
var section = configManager.GetSection<DatetimeFieldsConfig>();
if (section != null)
{
var dateTimeFields = section.DatetimeFields.Keys;
foreach (var field in dateTimeFields)
{
var dateTimeField = definitionList.FirstOrDefault(f => f.Name == field);
if (dateTimeField != null)
{
definitionList.Remove(dateTimeField);
var dateTimeFieldDefinition = Telerik.Sitefinity.Abstractions.ObjectFactory.Resolve<IFieldDefinition>();
dateTimeFieldDefinition.Name = dateTimeField.Name;
dateTimeFieldDefinition.Type = typeof(DateTime);
definitionList.Add(dateTimeFieldDefinition);
}
}
}
}
}
}
Once you implement the code in Visual Studio , build your solution and you will also have to reindex the index you are using from Administrator → Search Indexes → Action → Reindex
Register custom search service
In order to use your custom search service instead of the built-in one please refer to this documentation:
Register custom search service
Check field type in Hawksearch
After you have indexed please open your Hawksearch dashboard and check the type of the field you have created.
You can find them under Workbench → Data Configuration → Fields
It should look like this:
Extending Data Indexing in Hawksearch V4
Goal
This article provides information on how to extend the indexing logic for Hawksearch V4
- Make sure you have created at least one content item (e.g. News)
- Open Administration → Search indexes and create a new index CustomTestIndex
- Open it and make sure that News items or the content item you have created is selected
- Create a page and add the Hawksearch box and Hawksearch results widgets
- Select from the widget designer of both widgets to search in your CustomTestIndex
Register Custom Search Service
In order to use your custom search service instead of the built-in one you need to register it in the backend. Please refer to this documentation - Register custom search service
Setup search service
In order to add external data to the index you need to create a custom search service class which inherits the HawkseachService class and overrides the CreateIndex and UpdateIndex methods. Please refer to the code snippet below.
Add External Data to the Index
- Add additional Fields to the index
The string type of the field in the code bellow is used as an example.
using Hawksearch.Search;
using System.Collections.Generic;
using System.Linq;
using Telerik.Sitefinity.Services.Search;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
public override void CreateIndex(string sitefinityIndexName, IEnumerable<IFieldDefinition> fieldDefinitions)
{
var newFieldDefinition = Telerik.Sitefinity.Abstractions.ObjectFactory.Resolve<IFieldDefinition>();
newFieldDefinition.Name = "your-custom-field-name";
newFieldDefinition.Type = typeof(string);
var definitions = fieldDefinitions.ToList();
definitions.Add(newFieldDefinition);
base.CreateIndex(sitefinityIndexName, definitions);
}
}
}
- Add additional Fields to search Document
using Hawksearch.Search;
using System.Collections.Generic;
using Telerik.Sitefinity.Services.Search.Data;
using Telerik.Sitefinity.Services.Search.Model;
using Telerik.Sitefinity.Services.Search.Publishing;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
public override void UpdateIndex(string name, IEnumerable<IDocument> documents)
{
var documentList = new List<IDocument>();
foreach (var document in documents)
{
var fields = new List<IField>();
fields.AddRange(document.Fields);
var newField = new Field
{
Name = "your-custom-field-name",
Value = "your-custom-field-value"
};
fields.Add(newField);
var modifiedDocument = new Document(fields, document.IdentityField.Name);
documentList.Add(modifiedDocument);
}
base.UpdateIndex(name, documentList);
}
}
}
Once you implement the code in Visual Studio , build your solution and you will also have to reindex the index you are using from Administrator → Search Indexes → Action → Reindex
Expected results
Now if you Inspect your frontend page you should be able to find your custom field added to your document in the XHR search → results → document fields
Register custom search service
Goal
The goal of this article is to demonstrate how to register your custom search service
Register Custom Search Service
In order to use your custom search service instead of the built-in one you need to register it in the backend.
- Open the backend of your Sitefinity instance.
- Navigate to Administration → Settings (your-site-domain/Sitefinity/Administration/Settings)
- Go to Advanced (your-site-domain/Sitefinity/Administration/Settings/Advanced)
- Under Search → Search services → Hawksearch enter the TypeName of you custom search service (e.g. SitefinityWebApp.CustomSearchService)
- Save the changes
Index Nested Taxonomies
Goal
This article will provide information on how to extend the Hawksearch service to index Sitefinity’s Classifications with parent-child relations to create Nested Link List facets.
Add the Hierarchy Field
Hawksearch supports one Hierarchy field. You can select the field in Sitefinity’s Advanced Settings → Hawksearch (your-site-domain/Sitefinity/Administration/Settings/Advanced). The default field is “category”, but you can provide a custom field name.
After selecting the field you should provide the field name in the index “Additional fields for indexing“.
Navigate to Search indexes backend page (your-site-domain/Sitefinity/Administration/Search) and select an index. In Advanced section add the field name.
In the Hawksearch dashboard, the created field must be selected as the Hierarchy field. Open Workbench → Data Configuration → Fields and select the created field in the previous step, in this case Category. Make sure it is selected as Hierrchy Field before triggering reindexing from Sitefinity.
Extend the Hawksearch Service
In terms of performance, we want to control taxonomies for which types of content we want to export. In the example below, we provide a CustomContentType, in this case Car, for which we want to export the hierarchical taxonomies. You can easily add more than one content type by adding the full name of the Sitefinity content type to the validation on line 27. This will include the taxonomies selected for that content type and add them to the Hawksearch index.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Hawksearch.Search;
using Telerik.OpenAccess;
using Telerik.Sitefinity.Data;
using Telerik.Sitefinity.GenericContent.Model;
using Telerik.Sitefinity.Model;
using Telerik.Sitefinity.Publishing;
using Telerik.Sitefinity.Services.Search.Data;
using Telerik.Sitefinity.Taxonomies;
using Telerik.Sitefinity.Taxonomies.Model;
using Telerik.Sitefinity.Utilities.TypeConverters;
namespace SitefinityWebApp.Search
{
public class CustomSearchService : HawksearchService
{
private const string CustomContentType = "Telerik.Sitefinity.DynamicTypes.Model.Cars.Car";
public override void UpdateIndex(string name, IEnumerable<IDocument> documents)
{
var documentList = documents.ToList();
var contentType = this.GetContentType(documentList);
if (string.Equals(contentType, CustomContentType, StringComparison.InvariantCultureIgnoreCase))
{
var documentIds = documents.Select(d => d.Fields.FirstOrDefault(f => f.Name == "Id")?.Value.ToString());
var mappedManager = ManagerBase.GetMappedManager(contentType);
var type = TypeResolutionService.ResolveType(contentType, false);
var taxonomyManager = TaxonomyManager.GetManager();
var items = mappedManager
.GetItems(type, string.Empty, string.Empty, 0, 0)
.Cast<IDataItem>()
.Where(i =>
i.GetPropertyValue<ContentLifecycleStatus>("Status") == ContentLifecycleStatus.Live
&& i.GetPropertyValue<bool>("Visible")
&& documentIds.Contains(i.Id.ToString()))
.ToList();
var taxonomies = taxonomyManager.GetTaxa<HierarchicalTaxon>().ToList();
foreach (var document in documentList.Cast<Telerik.Sitefinity.Services.Search.Model.Document>())
{
var id = document.Fields.FirstOrDefault(f => f.Name == "Id");
var documentId = Guid.Empty;
if (id != null)
{
documentId = Guid.Parse(id.Value.ToString());
}
var properties = TypeDescriptor.GetProperties(items.FirstOrDefault(m => m.Id == documentId));
foreach (PropertyDescriptor property in properties)
{
if (property != null)
{
if (property.PropertyType == typeof(TrackedList<Guid>))
{
var taxonomyIds = items.FirstOrDefault(m => m.Id == documentId).GetPropertyValue<TrackedList<Guid>>(property.Name);
var taxonomyNames = new List<string>();
foreach (var taxonomyId in taxonomyIds)
{
var taxon = taxonomies.FirstOrDefault(t => t.Id == taxonomyId);
if (taxon != null && taxon.Parent != null)
{
taxonomyNames = this.ProcessTaxonomies(taxon);
}
}
if (document.Fields.FirstOrDefault(f => f.Name == property.Name) != null)
{
document.Fields.FirstOrDefault(f => f.Name == property.Name).Value = taxonomyNames.ToArray();
}
}
}
}
}
}
base.UpdateIndex(name, documents);
}
private string GetContentType(List<IDocument> documentList)
{
var contentType = string.Empty;
var doc = documentList.FirstOrDefault();
if (doc != null && doc.Fields.FirstOrDefault(f => f.Name == "ContentType") != null)
{
var contentTypeField = doc.Fields.FirstOrDefault(f => f.Name == "ContentType").Value;
if (contentTypeField != null)
{
contentType = contentTypeField.ToString();
}
}
return contentType;
}
private List<string> ProcessTaxonomies(HierarchicalTaxon taxon)
{
var taxonomies = new List<string>();
taxonomies.Add(taxon.Id.ToString());
while (taxon.ParentId != Guid.Empty)
{
taxonomies.Add(taxon.ParentId.ToString());
taxon = taxon.Parent;
}
taxonomies.Reverse();
return taxonomies;
}
}
}
Migration Options
Migrating the widget templates from version x.xxxx.x.29 or earlier to version x.xxxx.x.30 and above of the connector
Goal
The purpose of this article is to provide you with the necessary information in order to successfully migrate your widget templates from version x.xxxx.x.29 or earlier to version x.xxxx.x.30 and above of the Hawksearch Connector.
In order to address these changes, a configuration dropdown with the above-mentioned versions has been added to the Hawksearch configuration. You can find it under Administration → Settings → Advanced settings → Hawksearch.
The SearchBoxViewModel and the SearchViewModel which transfer the information to the widget templates no longer contain a property bool IsElastic {get; set;}
. The view models now contain the following property string Version {get; set;}
which holds the version which has been specified in the advanced settings. Please migrate to the new Version property and use it to do the necessary checks.
Previous widget template
Current widget template
Examples
Here are the current default widget templates for the HawksearchBox and HawksearchResults.
HawksearchBox
@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
{
// React template
if (!SystemManager.IsDesignMode)
{
@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/react-hawksearch.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/react-hawksearch-override.css"), "head")
}
<div data-component="react-search-box-bootstrap"
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-json-params="@Model.Data"
data-search-page="@Model.ResultsUrl"
data-index-name="@Model.Index"
data-current-culture="@Model.CurrentCulture">
</div>
<script data-translations="react-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"))"
}
</script>
// ***************************************************************************
// Vue template
@* @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 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
@model HawksearchWidgets.Mvc.ViewModels.Hawksearch.SearchViewModel
@using Telerik.Sitefinity.Frontend.Mvc.Helpers;
@using Telerik.Sitefinity.Services
@using Telerik.Sitefinity.Web
@using Newtonsoft.Json
@if (Model.Version == "V2L" || Model.Version == "V3L" || Model.Version == "V4L")
{
<div data-role="hawk-results" class="row hs-wrap" style="display: none;">
<div class="col-md-3 hs-col-3">
<div id="hawkbannerlefttop"></div>
<div id="hawkfacets"></div>
<div id="hawkbannerleftbottom"></div>
<input type="hidden" name="search-term" value="@Model.Keyword" />
</div>
<div role="main" class="col-md-9 hs-col-9" id="main-content">
<div id="hawktitle"></div>
<div class="right-bg">
<div id="hawkbannertop"></div>
<div id="hawktoptext"></div>
<div id="hawkrelated"></div>
<div id="hawktoppager"></div>
<div id="hawktoptext"></div>
<div id="hawkitemlist" class="item-list horizontal resource-listing clearfix">
</div>
<div id="hawkbottompager"></div>
<div class="clear"> </div>
</div>
</div>
</div>
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)
}
}
else if (!SystemManager.IsDesignMode)
{
if (Model.Languages.Length > 1 && Model.IsMultilingualEnabled)
{
<div>
<span>@Html.Resource("ChangeResultsLanguageLabel", "HawkWidgetsResources") </span>
@for (var i = 0; i < Model.Languages.Length; i++)
{
var language = Model.Languages[i];
var languageUrl = string.Format("{0}?language={1}", Model.UrlPath, language.Name);
<a href="@languageUrl">@language.DisplayName</a>
if (i < Model.Languages.Length - 2)
{
<span>, </span>
}
else if (i == Model.Languages.Length - 2)
{
<span> @Html.Resource("OrLabel", "HawkWidgetsResources") </span>
}
}
</div>
}
// React template
@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/react-hawksearch.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/react-hawksearch-override.css"), "head")
<div data-component="react-app-bootstrap"
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-show-searchbox="@Model.ShowSearchBox.ToString()"
data-current-culture="@Model.CurrentCulture">
</div>
<script data-translations="react-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="react-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>
// ***************************************************************
// Vue template
@* @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-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>
<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> *@
}
Migrating the widget templates from version x.xxxx.x.34 or earlier to version x.xxxx.x.35 and above of the Connector
Goal
This article provides information on how to upgrade the connector to the latest version (35) and above.
Please note that the Hawksearch.Default.cshtml and HawksearchBox.Default.cshtml templates will be overridden. If you have any customizations, please, save them under a different name.
What is new in the latest version?
The latest version of the connector provides a better experience when using the Hawksearch functionalities. It provides the option to have index multilingual content, deferred index activation and better React and Vue experience.
Migrating the Hawksearch result template
- Open your SitefinityWebApplication → ResourcePackages → MVC → Views
- Make sure that there are the two folders: Hawksearch and HawksearchBox
- Open the Hawksearch folder and the Hawksearch.Default.cshtml. This is the template of the current Hawksearch version. There are this changes that you’ll need to implement in you templates also.
a. One using is added QueryStringHelper :
b. The logic about the language visualization is also changed:
Before:
Now:
c. In the Vue template the script “vue-mappings“ is deleted. The above shown lines are not part of the Vue template any more.
Before:
d. On the Vue template the “Narrow Results“ in the vue-translations script is changed to “Filter By”.
Before:
Now:
- Using the information above the templates should be changed when updrading to the latest version of the connector.
Migrating the HawksearchBox result template
- Open your SitefinityWebApplication → ResourcePackages → MVC → Views
- Make sure that there are the two folders: Hawksearch and HawksearchBox
- Open the Hawksearch folder and the HawksearchBox.Default.cshtml. This is the template of the current HawksearchBox version. There are this changes that you’ll need to implement in you templates also.
a. The hawkCssUrl is changed. Before was accessed using the Helpers and now using the UrlResolver.
Before:
Now:
b. On the Vue template the “Narrow Results“ in the vue-translations script is changed to “Filter By”.
Before:
Now:
Examples
Here are the current default widget templates for the HawksearchBox and HawksearchResults.
HAWKSEARCH
@model HawksearchWidgets.Mvc.ViewModels.Hawksearch.SearchViewModel
@using Telerik.Sitefinity.Frontend.Mvc.Helpers;
@using Telerik.Sitefinity.Services
@using Telerik.Sitefinity.Web
@using Newtonsoft.Json
@using QueryStringHelper = HawksearchWidgets.Mvc.Helpers.QueryStringHelper;
@if (Model.Version == "V2L" || Model.Version == "V3L" || Model.Version == "V4L")
{
<div data-role="hawk-results" class="row hs-wrap" style="display: none;">
<div class="col-md-3 hs-col-3">
<div id="hawkbannerlefttop"></div>
<div id="hawkfacets"></div>
<div id="hawkbannerleftbottom"></div>
<input type="hidden" name="search-term" value="@Model.Keyword" />
</div>
<div role="main" class="col-md-9 hs-col-9" id="main-content">
<div id="hawktitle"></div>
<div class="right-bg">
<div id="hawkbannertop"></div>
<div id="hawktoptext"></div>
<div id="hawkrelated"></div>
<div id="hawktoppager"></div>
<div id="hawktoptext"></div>
<div id="hawkitemlist" class="item-list horizontal resource-listing clearfix">
</div>
<div id="hawkbottompager"></div>
<div class="clear"> </div>
</div>
</div>
</div>
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)
}
}
else if (!SystemManager.IsDesignMode)
{
if (Model.Languages.Length > 1 && Model.IsMultilingualEnabled)
{
<div>
<span>@Html.Resource("ChangeResultsLanguageLabel", "HawkWidgetsResources") </span>
@for (var i = 0; i < Model.Languages.Length; i++)
{
var language = Model.Languages[i];
var indexName = string.Empty;
var uri = this.Request.Url;
if (uri != null)
{
var query = HttpUtility.ParseQueryString(uri.Query);
indexName = query.Get("indexName");
}
var languageUrl = string.Format(string.IsNullOrWhiteSpace(indexName) ? "{0}?language={1}" : "{0}?language={1}&indexName={2}", Model.UrlPath, language.Name, indexName);
<a href="@languageUrl">@language.DisplayName</a>
if (i < Model.Languages.Length - 2)
{
<span>, </span>
}
else if (i == Model.Languages.Length - 2)
{
<span> @Html.Resource("OrLabel", "HawkWidgetsResources") </span>
}
}
</div>
}
// React template
@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/react-hawksearch.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/react-hawksearch-override.css"), "head")
<div data-component="react-app-bootstrap"
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-show-searchbox="@Model.ShowSearchBox.ToString()"
data-current-culture="@Model.CurrentCulture">
</div>
<script data-translations="react-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="react-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>
// ***************************************************************
// Vue template
@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-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>
<script data-translations="vue-translations" type="application/json">
{
"Filter By": "@Html.HtmlSanitize(Html.Resource("FilterBy", "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>
}
HAWLSEARCH BOX
@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.URL.UrlResolver.GetUrl().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
{
// React template
if (!SystemManager.IsDesignMode)
{
@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/react-hawksearch.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/react-hawksearch-override.css"), "head")
}
<div data-component="react-search-box-bootstrap"
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-json-params="@Model.Data"
data-search-page="@Model.ResultsUrl"
data-index-name="@Model.Index"
data-current-culture="@Model.CurrentCulture">
</div>
<script data-translations="react-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"))"
}
</script>
// ***************************************************************************
// Vue template
@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 data-translations="vue-translations" type="application/json">
{
"Filter By": "@Html.HtmlSanitize(Html.Resource("FilterBy", "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>
}
Extending Data Indexing in Hawksearch V4L
Goal
This article provides information on how to extend the indexing logic for Hawksearch V4L.
Building a Custom Exporter
You can extend the default implementation of the Writer class using the IoCKernel in Global.asax on the Bootstrapper_Bootstrapped method.
@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.URL.UrlResolver.GetUrl().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
{
// React template
if (!SystemManager.IsDesignMode)
{
@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/react-hawksearch.css"), "head")
@Html.StyleSheet(Url.WidgetContent("assets/dist/react-hawksearch-override.css"), "head")
}
<div data-component="react-search-box-bootstrap"
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-json-params="@Model.Data"
data-search-page="@Model.ResultsUrl"
data-index-name="@Model.Index"
data-current-culture="@Model.CurrentCulture">
</div>
<script data-translations="react-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"))"
}
</script>
// ***************************************************************************
// Vue template
@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 data-translations="vue-translations" type="application/json">
{
"Filter By": "@Html.HtmlSanitize(Html.Resource("FilterBy", "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>
}
Add External Data to the Index
Add additional Field to single V4L Document:
- Create a class (CustomWriterClass).
- Inherit the Writer class from the Hawksearch.SDK.Export.IO
- Override the method AppendAttribute() and add your custom field.
using System.Collections.Generic;
using System.Text;
using Hawksearch.SDK.Export.IO;
using Hawksearch.SDK.Indexing;
namespace HawksearchApp.Controllers
{
public class CustomWriterClass : Writer
{
protected override void AppendAttribute(string fieldName, List<string> fieldValues, SubmitDocument submitDoc, ref StringBuilder attributesSb)
{
var attributesLineTemplate = "{0}\t{1}\t{2}";
var id = submitDoc.IdentityField["IdentityField"];
attributesSb.AppendLine(
string.Format(
attributesLineTemplate,
id,
"your-custom-field-name",
"your-custom-field-value"));
base.AppendAttribute(fieldName, fieldValues, submitDoc, ref attributesSb);
}
}
}
Export Sitefinity's Page Content in Hawksearch V4L
Goal
This article provides information on how to export Sitefinity’s page content in Hawksearch V4L.
Building a Custom Exporter
You can extend the default implementation of the DataLoader class using the IoCKernel in Global.asax on the Bootstrapper_Bootstrapped method.
using Hawksearch.SDK;
using Hawksearch.SDK.Export;
using SitefinityWebApp.Hawksearch;
using System;
using Telerik.Sitefinity.Abstractions;
namespace SitefinityWebApp
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
Bootstrapper.Bootstrapped += this.Bootstrapper_Bootstrapped;
}
private void Bootstrapper_Bootstrapped(object sender, EventArgs e)
{
IocKernel.Kernel.Rebind<IDataLoader>().To<CustomDataLoader>();
}
}
}
Export Page Content
By default, the content of the page is not indexed. To export the content of the page, you need to extend the default DataLoader class and add the logic below, which is responsible for exporting the page content and striping the html from it.
- Add the logic for exporting page content to your custom exporter class.
using System.Collections.Generic;
using System.Globalization;
using Hawksearch.Export;
using Telerik.Sitefinity.Model;
using Telerik.Sitefinity.Pages.Model;
using Telerik.Sitefinity.Web.ResourceCombining;
namespace SitefinityWebApp.Search
{
public class CustomDataLoader : DataLoader
{
protected override IEnumerable<string> GetFieldValue(IDataItem item, string fieldName, CultureInfo culture = null)
{
if (item.GetType() == typeof(PageNode) && fieldName == "Page")
{
var pnItem = (PageNode)item;
InMemoryPageRender renderer = new InMemoryPageRender();
var memPage = renderer.RenderPage(pnItem, false, true);
var result = new List<string>();
var cleanHtml = this.StripHTML(memPage).Replace("\n", string.Empty).Trim();
cleanHtml = cleanHtml.Replace("\"", "\"\"");
cleanHtml = "\"" + cleanHtml + "\"";
result.Add(cleanHtml);
return result;
}
if (item.GetType() == typeof(PageNode) && fieldName == "Description")
{
var pnItem = (PageNode)item;
var pageData = pnItem.GetPageData();
var result = new List<string>();
if (pageData != null)
{
var final = this.StripHTML(pageData.Description.ToString()).Replace("\n", string.Empty).Trim();
final = final.Replace("\"", "\"\"");
if (!string.IsNullOrWhiteSpace(final))
{
result.Add(final);
}
}
else
{
result.Add(string.Empty);
}
return result;
}
return base.GetFieldValue(item, fieldName, culture);
}
private string StripHTML(string source)
{
try
{
string result;
// Remove HTML Development formatting
// Replace line breaks with space
// because browsers inserts space
result = source.Replace("\r", " ");
// Replace line breaks with space
// because browsers inserts space
result = result.Replace("\n", " ");
// Remove step-formatting
result = result.Replace("\t", string.Empty);
// Remove repeating spaces because browsers ignore them
result = System.Text.RegularExpressions.Regex.Replace(result,
@"( )+", " ");
// Remove the header (prepare first by clearing attributes)
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<( )*head([^>])*>", "<head>",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"(<( )*(/)( )*head( )*>)", "</head>",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
"(<head>).*(</head>)", string.Empty,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// remove all scripts (prepare first by clearing attributes)
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<( )*script([^>])*>", "<script>",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"(<( )*(/)( )*script( )*>)", "</script>",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
//result = System.Text.RegularExpressions.Regex.Replace(result,
// @"(<script>)([^(<script>\.</script>)])*(</script>)",
// string.Empty,
// System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"(<script>).*(</script>)", string.Empty,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// remove all styles (prepare first by clearing attributes)
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<( )*style([^>])*>", "<style>",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"(<( )*(/)( )*style( )*>)", "</style>",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
"(<style>).*(</style>)", string.Empty,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// insert tabs in spaces of <td> tags
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<( )*td([^>])*>", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// insert line breaks in places of <BR> and <LI> tags
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<( )*br( )*>", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<( )*li( )*>", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// insert line paragraphs (double line breaks) in place
// if <P>, <DIV> and <TR> tags
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<( )*div([^>])*>", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<( )*tr([^>])*>", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<( )*p([^>])*>", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Remove remaining tags like <a>, links, images,
// comments etc - anything that's enclosed inside < >
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<[^>]*>", string.Empty,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// replace special characters:
result = System.Text.RegularExpressions.Regex.Replace(result,
@" ", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"•", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"‹", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"›", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"™", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"⁄", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"<", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@">", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"©", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
@"®", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Remove all others. More can be added, see
// http://hotwired.lycos.com/webmonkey/reference/special_characters/
result = System.Text.RegularExpressions.Regex.Replace(result,
@"&(.{2,6});", string.Empty,
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// for testing
//System.Text.RegularExpressions.Regex.Replace(result,
// this.txtRegex.Text,string.Empty,
// System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// make line breaking consistent
result = result.Replace("\n", " ");
// Remove extra line breaks and tabs:
// replace over 2 breaks with 2 and over 4 tabs with 4.
// Prepare first to remove any whitespaces in between
// the escaped characters and remove redundant tabs in between line breaks
result = System.Text.RegularExpressions.Regex.Replace(result,
"(\r)( )+(\r)", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
"(\t)( )+(\t)", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
"(\t)( )+(\r)", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
result = System.Text.RegularExpressions.Regex.Replace(result,
"(\r)( )+(\t)", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Remove redundant tabs
result = System.Text.RegularExpressions.Regex.Replace(result,
"(\r)(\t)+(\r)", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Remove multiple tabs following a line break with just one tab
result = System.Text.RegularExpressions.Regex.Replace(result,
"(\r)(\t)+", " ",
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
// Initial replacement target string for line breaks
string breaks = "\r\r";
// Initial replacement target string for tabs
string tabs = "\t\t\t\t\t";
for (int index = 0; index < result.Length; index++)
{
result = result.Replace(breaks, " ");
result = result.Replace(tabs, " ");
}
// That's it.
return result;
}
catch
{
return source;
}
}
}
}
Updated about 1 year ago