World is now on Opti ID! Learn more

Linus Ekström
Dec 10, 2013
  8717
(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
Make Global Assets Site- and Language-Aware at Indexing Time

I had a support case the other day with a question around search on global assets on a multisite. This is the result of that investigation. This co...

dada | Jun 26, 2025

The remote server returned an error: (400) Bad Request – when configuring Azure Storage for an older Optimizely CMS site

How to fix a strange issue that occurred when I moved editor-uploaded files for some old Optimizely CMS 11 solutions to Azure Storage.

Tomas Hensrud Gulla | Jun 26, 2025 |

Enable Opal AI for your Optimizely products

Learn how to enable Opal AI, and meet your infinite workforce.

Tomas Hensrud Gulla | Jun 25, 2025 |

Deploying to Optimizely Frontend Hosting: A Practical Guide

Optimizely Frontend Hosting is a cloud-based solution for deploying headless frontend applications - currently supporting only Next.js projects. It...

Szymon Uryga | Jun 25, 2025

World on Opti ID

We're excited to announce that world.optimizely.com is now integrated with Opti ID! What does this mean for you? New Users:  You can now log in wit...

Patrick Lam | Jun 22, 2025

Avoid Scandinavian Letters in File Names in Optimizely CMS

Discover how Scandinavian letters in file names can break media in Optimizely CMS—and learn a simple code fix to automatically sanitize uploads for...

Henning Sjørbotten | Jun 19, 2025 |