3

This question is about the best/simple way to achieve the website search functionality.

There are pages in the site, and each is made up of few components. When the end user searches for a keyword, it should return the list of all pages that have that keyword.

I have followed this example, which will store all the text field values of every rendering in a page, when indexed. It works but its only for the text fields.

Many of the components in my site have datasources that have treelist fields. So, I also need to search all those selected items for the keyword.

[There are many such treelist fields, each for a different template item]

  1. Is there a way, where we can know if an item is being used in any page, either as a datasource or as one of the selected item in that datasource's treelist field.

  2. What if I use the metadata. Metadata is a template in every page which has title and description, but the thing is the content author has to keep updating it. Not sure if this approach is correct.

My research on the search techniques online only gave me articles that talk about how to configure search, the first part. It's good, but it only gives the dump of the indexed content. There is a major second part on how to push that content to UI, which I could never find.

Any help about the best way to start or go about with this is appreciated.

  • It is a bit difficult to suggest something without understanding your site structure. Where you store your datasources? Does each page has own list of data sources (dependencies) or not? The algoritm of calculating of related content depends on it. – Artsem Prashkovich Oct 11 '17 at 8:46
  • @ArtsemPrashkovich The datasources reside separately but in the same tree. All pages are under the Home item. If there is anything else that might help you give an idea, I will try to provide that. Btw i'm also available on slack ...in case...Thank you. – Qwerty Oct 11 '17 at 8:58
  • How can I contact you in Slack? Do you need my email to send an invite me? – Artsem Prashkovich Oct 11 '17 at 9:05
  • @ArtsemPrashkovich akshaysura.com/2015/10/27/… – Anton Oct 11 '17 at 9:17
4

If item selected in the Treelist or in the multilist, it will be mentioned in the Links Database. You can use that for getting a content related to the item. It means that you need to get a related items for each datasource item which you already got using code from example.

For getting related items you need to use the following method:

ItemLink[] itemLinks = Sitecore.Globals.LinkDatabase.GetReferences(datasourceItem);

Please, take into account that GetReferences method will return ALL related items like Renderings, Templates and etc. You need to add some filters becouse you shouldn't process templates and renderings items as it will not contain any relevant content.

Example:

public class SubcontentField : IComputedIndexField
    {
        public object ComputeFieldValue(IIndexable indexable)
        {
            var sitecoreIndexable = indexable as SitecoreIndexableItem;

            if (sitecoreIndexable == null) return null;

            // find renderings with datasources set
            var customDataSources = ExtractRenderingDataSourceItems(sitecoreIndexable.Item);

            var relatedItems = customDataSources.SelectMany(GetRelatedItems);

            // extract text from data sources
            var contentToAdd = customDataSources.SelectMany(GetItemContent).ToList();
            contentToAdd.AddRange(relatedItems.SelectMany(GetItemContent));

            if (contentToAdd.Count == 0) return null;

            return string.Join(" ", contentToAdd);
        }

        /// <summary>
        /// Finds all renderings on an item's layout details with valid custom data sources set and returns the data source items.
        /// </summary>
        protected virtual IEnumerable<Item> ExtractRenderingDataSourceItems(Item baseItem)
        {
            string currentLayoutXml = LayoutField.GetFieldValue(baseItem.Fields[FieldIDs.LayoutField]);
            if (string.IsNullOrEmpty(currentLayoutXml)) yield break;

            LayoutDefinition layout = LayoutDefinition.Parse(currentLayoutXml);

            // loop over devices in the rendering
            for (int deviceIndex = layout.Devices.Count - 1; deviceIndex >= 0; deviceIndex--)
            {
                var device = layout.Devices[deviceIndex] as DeviceDefinition;

                if (device == null) continue;

                // loop over renderings within the device
                for (int renderingIndex = device.Renderings.Count - 1; renderingIndex >= 0; renderingIndex--)
                {
                    var rendering = device.Renderings[renderingIndex] as RenderingDefinition;

                    if (rendering == null) continue;

                    // if the rendering has a custom data source, we resolve the data source item and place its text fields into the content to add
                    if (!string.IsNullOrWhiteSpace(rendering.Datasource))
                    {
                        // DataSourceHelper is a component of Blade
                        var dataSource = DataSourceHelper.ResolveDataSource(rendering.Datasource, baseItem);

                        if (dataSource != baseItem)
                        {
                            yield return dataSource;
                        }
                    }
                }
            }

        }

        protected List<Item> GetRelatedItems(Item item)
        {
            var result = new List<Item>();
            ItemLink[] itemLinks = Sitecore.Globals.LinkDatabase.GetReferences(item);

            for (int index = 0; index < itemLinks.Length; index++)
            {
                ItemLink itemLink = itemLinks[index];

                Item relatedItem = itemLink.GetTargetItem();

                if (relatedItem != null) //You need to put some conditions here. Something like: relatedItem.Paths.IsContentItem
                {
                    result.Add(relatedItem);
                }
            }

            return result;
        }

        /// <summary>
        /// Extracts textual content from an item's fields
        /// </summary>
        protected virtual IEnumerable<string> GetItemContent(Item dataSource)
        {
            foreach (Field field in dataSource.Fields)
            {
                // this check is what Sitecore uses to determine if a field belongs in _content (see LuceneDocumentBuilder.AddField())
                if (!IndexOperationsHelper.IsTextField(new SitecoreItemDataField(field))) continue;

                string fieldValue = (field.Value ?? string.Empty).StripHtml();

                if (!string.IsNullOrWhiteSpace(fieldValue)) yield return fieldValue;
            }
        }

        public string FieldName { get; set; }
        public string ReturnType { get; set; }
    }
  • ComputeFieldValue will fire when rebuilding an index and also on publish. So no need to keep rebuilding indexes for every change in the content – Qwerty Oct 11 '17 at 11:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.