Custom Sitecore field for storing Key/Value Data with Lookups

On a recent question about storing custom field attributes for a field in Sitecore on Stackoverflow just over a week ago I suggested looking at the Name Value List and Name Lookup List field types. I have to admit that I have never used this field before, but knew of their existence and the general premise of the field.

Name Value List field

If you are unaware of this field, it allows you to store Key/Value pairs of data. The underlying raw value of the field is then stored as url type parameters:

key1=value1&key2=value2&key3=value3

The great thing about this field is that you do not need to specify all the possible fields in a Template beforehand, it is much more dynamic allowing you to add values as required. There is a great post from Shriroop Parikh about using the Name Value List field and that post explains exactly my reasoning behind suggesting this type of field in the context of the original question – the need to add some similar data about a product of which there may be over 100 fields. In my experience when the choice becomes this large it is due to trying to create a single data template for all products, of which not all fields will be required for all products.

In this particular instance, the Name Value or Lookup List were not a good fit, but I still felt it was a good basis for the type of data that needed to be represented.

I created a new control inheriting from NameLookupValue, the control is essentially a reverse of the Name Lookup List field, instead of looking up the Value we want to lookup the Key and allow the user to manually enter the value. The code is fairly simple copy/paste operation, but I had to do the usual copying out of some methods in order to change a single line. It would have been much simpler if there was a protected virtual method we could override for creating the the Key input control as well, like there is for the Value field. I also had to remove the validation, which checks for non-aplhanumeric value in the key field (we are storing the GUID of the lookup Item) and also some regex which stripped out the non-aplha characters. One other gotcha was I had to call this.Controls.Clear(); first in the BuildControl(), this is due to the fact we call base.OnLoad(e) which in turn calls the BuildControl() method on the inherited class and adds the default NameValueList controls.

To use the control, first create some lookup values. These can be based on any template type and can store additional information which you may require:

NameValueList - Lookup Values

To use the new type, add the field to your template and set the datasource to the lookup.

NameValueList - Field Defintion

Now when we go to our product template we can add as many properties as we like. Each property can only be set once, if we add the same property again then the first one is evicted. You could change this behaviour, but this how the default Sitecore control works also.

NameLookupList

The underlying raw data that is now stored is a key/value pair with GUIDs instead:

{C3C79F47-5269-4AC1-B34A-F0924111ABA0}=123&{35FCE4F4-C3E2-444C-9CE1-D90ACD0BFFDC}=567

We end up with a much cleaner template, which will be easier to maintain and should hopefully be much easier for the content editors to use. Don’t forget about the Page Editor either and make the field accessible from there too.

How to create the custom field:

  • Add the custom field in `/sitecore/system/Field types`, which you can find in `core` database. We’ll use control sources so set the `Control` value to `contentExtension:NameLookupList`. The value after the semi-colon should match the class name of your control.
  • Patch in the custom field control into your Sitecore config:
    <controlSources>
      <source mode="on" namespace="MyProject.Shell" assembly="MyProject" prefix="contentExtension"/>
    </controlSources>
  • Add the code, build, deploy.

To use the field in your frontend:

    NameValueCollection nameValueCollection = Sitecore.Web.WebUtil.ParseUrlParameters(&amp;quot;&amp;quot;);
    foreach (var key in nameValueCollection.Keys)
    {
        // Do Stuff
        Item lookupItem = Sitecore.Context.Database.GetItem(MainUtil.GetID(key));
        string property = lookupItem["Chemical Property"];
        string unit = lookupItem["Chemical Unit"];
        string unitValue = nameValueCollection[key.ToString()];
    }

Or more simply bind it to your fieldrenderer (add in the repeater code as required):

<FieldRenderer ID="frChemicalProperty" runat="server" FieldName="Chemical Property" />;
frChemicalProperty.DataSource = key;

I won’t bore you with all the code for the control, you can find it in this Github Gist.

EDIT

The above solution was not suitable, so I amended the control. Still not sure if this will do what is required, but this would be my solution given the limited knowledge. That’s the thing with coding, esp Sitecore, there’s a million ways to achieve the same goal!

Code in Github Gist: https://gist.github.com/jammykam/d935b2c5b6054ca10282

Custom-Lookup-Control

Custom-Lookup-Control-Raw-Value

Custom-Lookup-Control-Fields

Additional reading:

Advertisements

7 comments

  1. Aodhan · May 20, 2015

    Hey, I’ve loosely implemented this on a project I’m working on however I’m now running into the problem of the contend editors being prompted with the “do you want to save the changes to the item” message each time they navigate away from an item that has the custom field type included, even if no changes have been made. I’m at a bit of a loss as to how to override this behaviour as I’ve double checked and it’s definitely the custom field type that is causing this problem.

    • jammykam · May 20, 2015

      I’ll take a look and get back to you with an update.

      • Aodhan · May 22, 2015

        Thanks. I’ve figured that it’s related to the following function:

        protected override void SetModified()
        {

        }

        But am unsure how to set the modified flag correctly.

      • jammykam · June 11, 2015

        Sorry for the late reply. The issue is the inheritance and the base.OnLoad() call, which in turn checks the value against the original implementation of the control. Being a bit lazy and since I don’t rally want to copy paste all the base code as well, the simplest solution is to reset the values and then run our own load code:

        protected override void OnLoad(EventArgs e)
        {
        Assert.ArgumentNotNull((object)e, “e”);

        string origValue = this.Value;
        base.OnLoad(e);
        this.Value = origValue; // reset the value back
        Sitecore.Context.ClientPage.Modified = false; // if values have actually changed our code below will set this to true

        urlString = new UrlString(this.Value);
        if (Sitecore.Context.ClientPage.IsEvent)
        this.LoadValue();
        else
        this.BuildControl();
        }

  2. Lee · December 17, 2015

    Hi, I’m attempting to implement your solution on my site but I am having difficulty. Instead of getting a drop down list and the ability to add new rows like a reversed Name Lookup Value List field I am getting a long list of all of the options available in the first field and no ability to add more items. One difference in my site is that I do not have a separate sitecore template with a field to associate the value to like you do with your chemical unit field on the chemical type template. Instead, my value can be anything. I want my key to be selected from a list of existing pages and my value to be an empty field. I have a couple of theories where this could have gone wrong. I wasn’t sure where to place my new class within my Sitecore project. I put it in a folder under MyProject.Web > Sitecore > Shell > Fields. I also was not sure how to change the section of the code that reads:
    private string BuildParameterKeyValue(Item item)
    {
    Assert.ArgumentNotNull(item, “item”);
    string uniqueId = GetUniqueID(this.ID + “_Param”);
    string vertical = this.IsVertical ? “” : string.Empty;
    string layout = “{0}{1}{4}{2} {3}”;

    return layout.FormatWith(
    item[“Chemical Property”],
    GetHtmlControl(uniqueId, null, “hidden”, item.ID.ToString()),
    GetHtmlControl(uniqueId, “_value”, “text”, urlString.Parameters[item.ID.ToString()]),
    item[“Chemical Unit”],
    (object) vertical);
    }

    Can you offer any assistance?

    • jammykam · December 18, 2015

      Hi. I would suggest you go with the first example given, with the drop down selection list for the options, that sounds more like what you need…

      The list of options is built up on line 112, which is also where the HTML markup is regenerated that is output to in the editor: https://gist.github.com/jammykam/de0dabe2ac4612fe649e#file-namelookuplist-L112

      You need to set the start location of the item in the source field in your template, like you do for multilist for example. If you some different logic then set the list of Items on that line with your own.

      The project does not matter, as long as it referenced correctly. I do tend to put it into a “MyProject.CMS.Custom” project out of habit and I like to keep it separate from the web project.

      Let me know if you need more help.

      • Lee · December 23, 2015

        That worked perfectly! Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s