Try our conversational search powered by Generative AI!

Linus Ekström
Nov 5, 2012
  13248
(2 votes)

Creating a component that searches for content

Update: you can download a 7.5 compatible version here: SearchComponentSourceCode.zip

In the third blog post in the series how to extend the user interface in EPiServer 7 we’ll look into how to create a more complex component. The goal in this case is to use some of the built in classes in EPiServer 7 to provide the user with a search box which will call the server to issue a page search and then present this to the user. Since we’ll use a built in class in EPiServer CMS to display the items which will give us automatic support for content type icon, click to go to the item as well as drag and drop support, for instance to a content area, for the items.

First, we add the component that will automatically plug in to the user interface.

[Component(
        Categories = "cms",
        WidgetType = "alloy.components.CustomSearch",
        //Define language path to translate Title/Description.
        //LanguagePath = "/episerver/cms/components/tasks";
        Title = "Custom Search",
        Description = "Test of custom search",
        SortOrder = 120,
        PlugInAreas = PlugInArea.AssetsPanel
        )]
public class CustomSearch
{ }

Then we will add the client widget needed to display our component. Since we have previously added a namespace mapping from alloy => ClientResources/Scripts we add a file named ClientResources/Scripts/components/CustomSearch.js with the following content:

define([
// Dojo
    "dojo",
    "dojo/_base/declare",
    "dojo/dom-geometry",
 
// Dijit
    "dijit/_TemplatedMixin",
    "dijit/_Container",
    "dijit/layout/_LayoutWidget",
    "dijit/_WidgetsInTemplateMixin",
 
// EPi CMS
    "epi/cms/component/ContentQueryGrid",
    "dojo/text!./templates/CustomSearch.html"
], function (
// Dojo
    dojo,
    declare,
    domGeometry,
 
// Dijit
    _TemplatedMixin,
    _Container,
    _LayoutWidget,
    _WidgetsInTemplateMixin,
 
// EPi CMS
    ContentQueryGrid,
    template
) {
 
    return declare("app.components.CustomSearch",
        [_Container, _LayoutWidget, _TemplatedMixin, _WidgetsInTemplateMixin], {
        // summary: This component enabled searching of content where the results will be displayed in a grid.
 
        templateString: template,
 
        postCreate: function () {
 
            this.inherited(arguments);
        },
 
        resize: function (newSize) {
            // summary:
            //      Customize the default resize method.
            // newSize: object
            //      The new size of the custom query component.
            // tags:
            //      Public
 
            this.inherited(arguments);
 
            var toolbarSize = domGeometry.getMarginBox(this.toolbar);
            var gridSize = { w: newSize.w, h: newSize.h - toolbarSize.h }
 
            this.contentQuery.resize(gridSize);
        },
 
        _reloadQuery: function () {
            this.contentQuery.set("queryParameters", { queryText: this.queryText.value });
            this.contentQuery.set("queryName", "CustomQuery");
        }
    });
});

Creating a template for the widget

One thing to mention is the following row in our define statement:

"dojo/text!./templates/CustomSearch.html"

What this does it telling the system that we want to load a text resource with the name/path “./templates/CustomSearch.html”. Dojo gives us the option to set up our child widgets programmatically as well as defining them in a text string which can either be declared as a string property or placed in an html file. In this case we create a file named “CustomSearch.html” and place it in a folder named “templates” located in the same folder as the editor. Inside the template we add the following markup:

<div>
    <div class="epi-gadgetInnerToolbar" data-dojo-attach-point="toolbar">
        <div data-dojo-type="dijit.form.TextBox"
            data-dojo-attach-point="queryText"
            data-dojo-props="intermediateChanges:true"
            data-dojo-attach-event="onChange: _reloadQuery">
        </div>
    </div>
    <div data-dojo-type="epi.cms.component.ContentQueryGrid" data-dojo-attach-point="contentQuery">
    </div>
</div>

If you take a look on the html in the template you can see some standard html stuff like elements and standard attributes like “class”. There are, however, also some html5-data-attributes defined which dojo will use when starting instances of the widget to simplify working with the nodes in the template. In this case we have used:

  • data-dojo-type: Converts the regular html element to a widget with the type specified in the attribute, for instance "dijit.form.TextBox".
  • data.dojo.attach-point: Makes it possible to access the node from within our widget with the given value, for instance ”toolbar”.
  • data-dojo-props: Adds properties defined in the attribute to the widget at start up. In this case we set the intermediateChanges property of the textbox to true.
  • data-dojo.attach-events: Connects an event of the widget to a method in our widget. In our case we attach to the onChange event of the textbox and our widgets method “_reloadQuery”.

Using ContentQueryGrid to display the results

Last in the template we add a widget with the type "epi.cms.component.ContentQueryGrid". This is a class in EPiServer CMS that is used to display listings of content to the user. This class depends on that a query that returns a list of content have been plugged into the server. We’ll add this shortly but let’s look on the parameters passed on to get the correct query and parameters. We implemented a method called _reloadQuery in our widget that will be called each time someone changes the textbox:

_reloadQuery: function () {
    this.contentQuery.set("queryParameters", { queryText: this.queryText.value });
    this.contentQuery.set("queryName", "CustomQuery");
}

What this code does is two things:

  1. It adds any custom parameters that we need to the query, in this case queryText.
  2. It sets the queryName property of the ContentQueryGrid to “CustomQuery” which will be used to find the query to execute.

Implementing the server side query

So far, we have added the definition of the component and the client side widget including the template for the widget. The only thing we have left is to implement the actual search query that will be executed:

using System.Collections.Generic;
using System.Linq;
using System.Web;
using EPiServer.Cms.Shell.UI.Rest.ContentQuery;
using EPiServer.Core;
using EPiServer.ServiceLocation;
using EPiServer.Shell.Search;
 
namespace EPiServer.Templates.Alloy.UIExtensions.CustomSearch
{
    [ServiceConfiguration(typeof(IContentQuery))]
    public class CustomSearchQuery : ContentQueryBase
    {
        private readonly IContentRepository _contentRepository;
        private readonly SearchProvidersManager _searchProvidersManager;
        private readonly LanguageSelectorFactory _languageSelectorFactory;
 
        public CustomSearchQuery(
            IContentQueryHelper queryHelper,
            IContentRepository contentRepository,
            SearchProvidersManager searchProvidersManager,
            LanguageSelectorFactory languageSelectorFactory)
            : base(queryHelper)
        {
            _contentRepository = contentRepository;
            _searchProvidersManager = searchProvidersManager;
            _languageSelectorFactory = languageSelectorFactory;
        }
 
        /// <summary>
        /// The key to trigger this query.
        /// </summary>
        public override string Name
        {
            get { return "CustomQuery"; }
        }
 
        protected override IEnumerable<IContent> GetContent(ContentQueryParameters parameters)
        {
            var queryText = HttpUtility.HtmlDecode(parameters.AllParameters["queryText"]);
            var searchQuery = new Query(queryText);
 
            var contentReferences = Enumerable.Empty<ContentReference>();
 
            var searchProvider = _searchProvidersManager.GetEnabledProvidersByPriority("CMS/Pages", true)
                .FirstOrDefault();
 
            if (searchProvider != null)
            {
                contentReferences =
                    searchProvider.Search(searchQuery).Select(
                        result => ContentReference.Parse(result.Metadata["Id"])).Distinct();
            }
 
            return _contentRepository.GetItems(contentReferences,
                _languageSelectorFactory.AutoDetect(parameters.AllLanguages));
        }
    }
}

 

The search class is pretty straight forward but a few things to mention is:

  • It uses the ServiceConfiguration attribute to plug in as a query to the content structure store which is one of the default REST stores in EPiServer CMS.
  • It inherits from ContentQueryBase
  • which is a base query implementation that handles some things like access rights filtering.
  • It overrides the GetContent method which does the actual search and return an IEnumerable of IContent.

That was the last thing we needed to do so lets compile and load up the user interface. We now have a component in the asserts pane. When we enter text in the textbox we issue an query and show the items the EPiServer-way:

Shows the component inside the EPiServer user interface

Note: If you have installed the new Alloy template package and try to run this you will probably not get any hits as you type. The reason for this is that the Alloy templates has configuration transforms enabled but unfortunately the configuration for episerver.search is added to the regular web.config file. To remedy this you can install search again on your site and move the search configuration from the regular web.config file to the web.config file placed in [configuration]/EPiServer.

This blog post is part of a series to extend the user interface in EPiServer 7.

Extending the User Interface of EPiServer 7

Plugging in a Dojo based component

Creating a content search component

Adding a more advanced property editor

Nov 05, 2012

Comments

Joshua Folkerts
Joshua Folkerts Nov 6, 2012 05:48 PM

FANTASTIC!!!! Care to share the source :)

Nov 15, 2012 09:30 AM

Yeah the source to the complete series would be great.

Joshua Folkerts
Joshua Folkerts Nov 21, 2012 05:17 AM

hey Linus, where is this namespace at. I cannot find it anywhere
EPiServer.Cms.Shell.UI.Rest.ContentQuery;

Thanks

Nov 22, 2012 11:14 AM

I have added the code to the series on the episerver nuget feed as the package EPiServer.UI.Samples. There are a few manual steps still:

You need to add references to "bin\episerver.ui.dll" and "modulesbin\EPiServer.Cms.Shell.UI.dll". (If someone knows how to add references to local assemblies in powershell, please get back to me and I'll update the package).

You might need to add the following line to your module.config file if you already have it since it does not seem to replace existing files:


Joshua Folkerts
Joshua Folkerts Dec 26, 2012 05:38 PM

Hey Linus, thanks for adding these.

Mari Jørgensen
Mari Jørgensen Apr 10, 2013 02:01 PM

Linus: Both this sample and Joel's PowerSlice module depends on having items of type IContent as list source. Is there built-in dojo classes for listing any type of objects?

To be more precise: I want to display a list video names - the videos are custom objects indexed by episerver find. I then want to specify drag-n-drop support for each item in list, ref http://joelabrahamsson.com/specifying-drag-n-drop-support-in-custom-episerver-editor-descriptor/

Mari Jørgensen
Mari Jørgensen Apr 11, 2013 10:47 AM

I guess a workaround is adding the videos as a content provider... not ideal, but probably better than "entering the dark side" = dojo :)

Björn Olsson
Björn Olsson Apr 25, 2013 04:50 PM

This example doesn't work in 7.1 (works fine in 7.0 with patch 2), the following error occurs when the gadget is loaded:

Unable to resolve constructor for: 'epi.cms.component.ContentQueryGrid'

I've tried to update the package references according to the releases notes:

http://world.episerver.com/Documentation/Items/Release-Notes/EPiServer-CMS/EPiServer-7/Release-Notes--EPiServer-7-1-CMS/

But it seams like the ContentQueryGrid.js no longer exists (?), I can't find it in the resource folder: "..\Modules\Shell\2.0.86\"

This functionality is exactly what we need for an ongoing project (together with some tweaks of course). I would be eternally grateful if this example would be updated for 7.1.

Björn Olsson
Björn Olsson Apr 25, 2013 06:00 PM

Managed to "re-define" the original ContentQueryGrid, so i got this example running in 7.1. But it's still interesting to know why ContentQueryGrid was removed in 7.1?

/Regards

Apr 25, 2013 09:15 PM

The epi/cms root namespace was renamed to epi-cms since Dojo 1.8 did not allow root namespaces with /. Try if renaming that dependency helps.

Eric Bruno
Eric Bruno Apr 26, 2013 09:12 AM

You have to change the dojo-data-type in the template as well, otherwise it will not be able to resolve it correctly :)

Fredrik von Werder
Fredrik von Werder Apr 25, 2014 03:48 PM

I'm having problems using this in 7.5 ...

I have changed all the epi/cms to epi-cms etc

In the console i see:
message: "Unable to resolve constructor for: 'epi-cms.component.ContentQueryGrid'"}

Is there any way to get the correct example for 7.5 ?

Apr 25, 2014 04:11 PM

I checked the code and it still exists. Try changing to epi-cms/component/ContentQueryGrid instead since the format was changed in the Dojo upgrade we did.

Fredrik von Werder
Fredrik von Werder Apr 25, 2014 04:15 PM

Yes, funny it seems like the changes in JS and html files didnt take affect until I changed the module.config ... now it is working :-)

However, the search doesn't work, I would like to search for pages beneath a certain node, but I will dig deeper into this myself before asking too much.

Fredrik von Werder
Fredrik von Werder May 5, 2014 03:33 PM

However, now when I am trying to do this in Epi 7.7 it all fails... changes?

Fredrik von Werder
Fredrik von Werder May 5, 2014 03:47 PM

It works it works, just a bad config :-)

May 5, 2014 04:04 PM

Great! :)

Brian Evans
Brian Evans Jul 25, 2014 06:25 PM

Linus -

Quick question, that looks to have been asked earlier. It looks like this code (and Joel's Powerslice) relies on IContentQuery. I do not believe this exists in 7.5. Is there a way you recommend working around that?

Thanks again,
Brian

Aug 1, 2014 06:45 PM

@Brian: I've added source code that works for 7.5 at the top of this page. Regarding the question about PowerSlice, it's true that it also uses content query. I tried making a 7.5 compatible version of PowerSlice earlier this week but unfortunately, it required more work than what I first expected. I'll try to continue getting a 7.5 compatible version when I get some more time to continue with this.

Jan 6, 2015 03:18 PM

Any luck with the PowerSlice update yet? I've found that PowerSlice could be useful for my current 7.18.xx project... :)

...


..ah - I search around, and found an update on GitHub that is only fifteen days old: https://github.com/episerver/PowerSlice

Perhaps this works? But in that case, shouldn't this be deployed as a NuGet update in the EPi-feed?

Jan 8, 2015 10:29 AM

Hi!

I talked with the team working with Power Slice and it's pretty much done. It will be released soon but you can download and try it out already our our github page: https://github.com/episerver/PowerSlice

Please login to comment.
Latest blogs
Optimizely and the never-ending story of the missing globe!

I've worked with Optimizely CMS for 14 years, and there are two things I'm obsessed with: Link validation and the globe that keeps disappearing on...

Tomas Hensrud Gulla | Apr 18, 2024 | Syndicated blog

Visitor Groups Usage Report For Optimizely CMS 12

This add-on offers detailed information on how visitor groups are used and how effective they are within Optimizely CMS. Editors can monitor and...

Adnan Zameer | Apr 18, 2024 | Syndicated blog

Azure AI Language – Abstractive Summarisation in Optimizely CMS

In this article, I show how the abstraction summarisation feature provided by the Azure AI Language platform, can be used within Optimizely CMS to...

Anil Patel | Apr 18, 2024 | Syndicated blog

Fix your Search & Navigation (Find) indexing job, please

Once upon a time, a colleague asked me to look into a customer database with weird spikes in database log usage. (You might start to wonder why I a...

Quan Mai | Apr 17, 2024 | Syndicated blog