A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Linus Ekström
Dec 10, 2013
  8760
(0 votes)

Auto suggest editor in EPiServer 7.5

Update: This functionality is now built in. Read more about it here.

This is an updated version of the editor that was done in a blog post around the release of EPiServer 7 about a year ago that has been simplified quite a lot (the original blog post can be found here). The goal of this blog post is to add an editor that can be used to select values for a property that is feed from a store using Web API 2.0. The result looks something like this:

AuthorSelection

As with the previos blog post the usage is to add a UI hint to a property:

public class MyPage : SitePageData
{
    [UIHint("author")]        
    public virtual string ResponsibleAuthor { get; set; }
}    

Then we add an editor descriptor that is responsible for defining the widget used for editing as well as sending the store URL to the widget:

using System;
using System.Collections.Generic;
using System.Web;
using EPiServer.Shell.ObjectEditing.EditorDescriptors;
 
namespace EPiServer.Templates.Alloy.Business.EditorDescriptors
{
    [EditorDescriptorRegistration(TargetType = typeof(string), UIHint = "author")]
    public class EditorSelectionEditorDescriptor : EditorDescriptor
    {
        public EditorSelectionEditorDescriptor()
        {
            ClientEditingClass = "alloy/editors/AutoSuggestEditor";
        }
 
        public override void ModifyMetadata(Shell.ObjectEditing.ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
        {
            base.ModifyMetadata(metadata, attributes);
 
            metadata.EditorConfiguration["storeurl"] = VirtualPathUtility.ToAbsolute("~/api/authors/");
        }
    }
}

Creating the service

I base the server side logic that is responsible to feed the widget suggestions on Web API 2.0 and attribute routing. I followed the following guide to set up the attribute routing (http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2). Please note that if you are running an existing project you need to add a call the config.MapHttpAttributeRoutes() method in the application start up as suggested in the guide to get attribute mappings to work.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
 
namespace EPiServer.Templates.Alloy.UIExtensions.Rest
{
    [RoutePrefix("api/authors")]
    public class AuthorWebStoreController : ApiController
    {
        private List<string> _editors = new List<string>{
            "Adrian", "Ann", "Anna", "Anne", "Linus", "Per",
            "Joel", "Shahram", "Ted", "Patrick", "Erica", "Konstantin", "Abraham", "Tiger"        
        };
 
        [Route]
        public IEnumerable<AuthorSearchResult> GetAuthors(string name)
        {
            //Remove * in the end of name
            if (name.EndsWith("*"))
            {
                name = name.Substring(0, name.Length - 1);
            }
 
            return QueryNames(name);
        }
 
        [Route("{name}")]
        public AuthorSearchResult GetAuthorByName(string name)
        {
            string author = _editors.FirstOrDefault(e => e.StartsWith(name, StringComparison.OrdinalIgnoreCase));
 
            return String.IsNullOrEmpty(author) ? null : new AuthorSearchResult { name = author, id = author };
        }
 
        private IEnumerable<AuthorSearchResult> QueryNames(string name)
        {
            IEnumerable<string> matches;
            if (String.IsNullOrEmpty(name) || String.Equals(name, "*", StringComparison.OrdinalIgnoreCase))
            {
                matches = _editors;
            }
            else
            {
 
                matches = _editors.Where(e => e.StartsWith(name, StringComparison.OrdinalIgnoreCase));
            }
            return matches
                .OrderBy(m => m)
                .Take(10)
                .Select(m => new AuthorSearchResult { name = m, id = m });
        }
    }
 
    public class AuthorSearchResult
    {
        public string name { get; set; }
        public string id { get; set; }
    }
}

Note(1) that the route prefix matches the URL sent to the editing widget.

Note(2) that the casing for the properties of the result class is using lower case. This is since the default json contract resolver in .NET web API uses a standard contract resolver instead of the camel case contract resolver that was used in the EPiServer Rest stores. This can of course be changed for the entire application but that’s not part of this blog post.

The editing widget

The editing widget has been greatly simplified, using inherritance instead of composition which in this case reduces complixity. (given that you have set up a script module, you can add the content below in a file placed under “ClientResources/Scripts/Editors/AutoSuggestEditor.js”)

define([
    "dojo/_base/declare",
    "dojo/store/JsonRest",
 
    "dijit/form/FilteringSelect"
],
function (
    declare,
    JsonRest,
 
    FilteringSelect
) {
 
    return declare([FilteringSelect], {
 
        postMixInProperties: function () {
 
            var store = new JsonRest(dojo.mixin({
                target: this.storeurl
            }));
 
            this.set("store", store);
 
            // call base implementation
            this.inherited(arguments);
        }
    });
});

Note that since the store is feed to the widget, the same widget can be used against different service APIs.

Required field and validation bug

When testing this i noticed that there seems to be a bug in the filteringselect widget when the widget has focus and is cleared. If the value is required then the validation will not be shown to the user if the search result is open. The second time the field is left (with an empty valur) the validation appears.

Dec 10, 2013

Comments

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP: Learning Optimizely Just Got Easier: Introducing the Optimizely Learning Centre

On the back of my last post about the Opti Graph Learning Centre, I am now happy to announce a revamped interactive learning platform that makes...

Graham Carr | Jan 31, 2026

Scheduled job for deleting content types and all related content

In my previous blog post which was about getting an overview of your sites content https://world.optimizely.com/blogs/Per-Nergard/Dates/2026/1/sche...

Per Nergård (MVP) | Jan 30, 2026

Working With Applications in Optimizely CMS 13

💡 Note:  The following content has been written based on Optimizely CMS 13 Preview 2 and may not accurately reflect the final release version. As...

Mark Stott | Jan 30, 2026

Experimentation at Speed Using Optimizely Opal and Web Experimentation

If you are working in experimentation, you will know that speed matters. The quicker you can go from idea to implementation, the faster you can...

Minesh Shah (Netcel) | Jan 30, 2026