custom property

2 posts

Custom EPiServer key value property with predefined keys support

I've created a custom property with support for predefined keys. You can check out the source code on GitHub and you can install it by simply running Install-Package JOS.PropertyKeyValueList in the Package manager console.

Before I started creating the property I did some googling and I found this blogpost by Peter Löfman where he had done something quite similar to what I wanted to achieve so I used his blog post to get started.

The property has two "modes", ReadOnlyKeys or Normal.

ReadOnlyKeys

Add the property like this to try it out.

[BackingType(typeof(PropertyKeyValueList))]
[KeyValue(typeof(ReadOnlyKeysProvider))]
public virtual IEnumerable<KeyValueItem> ReadOnly { get; set; }  

ReadOnlyMode is specified by passing in an IReadOnlyKeysProvider to the constructor of the KeyValueAttribute.

IReadOnlyKeysProvider

public interface IReadOnlyKeysProvider  
{
    List<string> GetKeys();
}

My implementation in this example looks like this:
ReadOnlyKeysProvider

public class ReadOnlyKeysProvider : IReadOnlyKeysProvider  
{
    public List<string> GetKeys()
    {
        return new List<string>()
        {
            "Arsenal",
            "Real Madrid",
            "Barcelona",
            "Skara FC",
            "Axvalls IF",
            "Juventus",
            "IFK Göteborg",
            "Djurgårdens IF",
            "AIK"
        };
    }
}

When specifying a IReadOnlyKeysProvider the property will render in ReadOnlyKeysMode and look like this:
Property rendered in readOnlyKeysMode The editor will only be able to edit the "value" portion of the property, not the predefined keys. It's not possible to add new items in this mode.

Normal

Add the property like this to try it out.

[BackingType(typeof(PropertyKeyValueList))]
[KeyValue]
public virtual IEnumerable<KeyValueItem> NotReadOnly { get; set; }  

The property will look like this in edit mode:
Property rendered in normal mode Currently it's possible to add as many items as you like but Im thinking of adding an optional MaxLimit.

Some notes

Im no DOJO expert but in all examples I've found online regarding custom EPiServer properties it looks like this.value should be available in the postCreate method. this.value is null for me in postCreate for some weird reason, but if I add the below code(wrapping it in a setTimeout block), it works? I've submitted a ticket to the developer support but if anyone have any idea of why it's null I would gladly accept your help :)

Ugly workaround for null value in postCreate

postCreate: function () {  
    this.readOnlyKeysMode = this.readOnlyKeys !== undefined && this.readOnlyKeys.length > 0;
    //This is a really ugly fix for a (possible) episerver bug? 
    //this.value is null in postCreate but if we wait 200ms, this.value suddenly isn't null anymore...?
    var that = this;
    setTimeout(function () {
        if (that.readOnlyKeysMode) {
            domStyle.set(that.kvlAddButton, {
                "display": "none"
            });
            array.forEach(that.readOnlyKeys, that._renderReadOnlyMode, that);
        } else {
            array.forEach(that.value, that._renderNormalMode, that);
        }
    }, 200);
}

UPDATE After consulting the support, Episerver changed some logic in a recent UI release, so instead of getting the value in postCreate, you can retrieve it in _setValueAttr. I will update my code asap to get rid of the ugly timeout.

How to create a custom EPiServer 7 property with Dojo

I've used a lot of custom EPiServer properties at work but I've never built my own. Luckily for me, one client at work gave me a new project to work on. It's somewhat like a blog page, he wants to display different articles on a page. The articles should be splitted up using Tags.

I was afraid at first, how do I build that? Sure, I could just let him enter the tags in a ordinary string field and separate the tags with a comma. But that is not a good solution at all.

Let's play with Dojo!

One note before I begin, I found this project(GETA.Tags) and I've looked at that project for inspiration/how I should structure the project and stuff like that...

Enough talk, time for some CODE!

The first thing you wanna do when creating a custom property is to create a EditorDescriptor. It looks like this:
JoTagsEditorDescriptor.cs

using EPiServer.Shell.ObjectEditing.EditorDescriptors;  
namespace PlayTime.Business.Properties.JoTags  
{
    [EditorDescriptorRegistration(TargetType = typeof(string), UIHint = "JoTagsProperty")]
    public class JoTagsEditorDescriptor : EditorDescriptor
    {
        public JoTagsEditorDescriptor()
        {
            ClientEditingClass = "app.editors.JoTagsProperty";
        }
    }
}

Then you will create the actuall "Property class". Here you could do some custom validation and stuff like that when saving/retrieving the data.
PropertyJoTags.cs

using System;  
using EPiServer.Core;  
using EPiServer.PlugIn;

namespace PlayTime.Business.Properties.JoTags  
{
[PropertyDefinitionTypePlugIn(Description = "A property for tagging content", DisplayName = "Jo Tags")]
    public class PropertyJoTags : PropertyString
    {
        public override Type PropertyValueType
        {
            get { return typeof(string); }
        }

        public override object Value
        {
            get
            {
                var value = base.Value as string;
                if (value == null)
                {
                    return string.Empty;
                }

                return value;
            }
            set
            {
                if (value == null)
                {
                    value = string.Empty;
                }

                base.Value = value;

            }
        }

        public override object SaveData(PropertyDataCollection properties)
        {
            return String;
        }
    }
}

That's pretty much the EPiServer side done. You will need to register your property in a file named module.config in your project root, if the file doesn't exists, create it. In that file you will register your property as well as your propertys dependencies(in my case, jQuery, jQuery UI and TagIt)

My file looks like this:
module.config

<module>  
    <assemblies>
        <add assembly="PlayTime" />
    </assemblies>

    <dojoModules>
        <add name="app" path="Scripts" />
        <add name="JoTags" path="JoTags" />
    </dojoModules>

    <clientResources>
        <add name="JoTagsVendor" resourceType="Script" sortIndex="1" path="JoTags/Vendor/jquery-1.11.2.min.js" />
        <add name="JoTagsVendor" resourceType="Script" sortIndex="2" path="JoTags/Vendor/jquery-ui.min.js" />
        <add name="JoTagsVendor" resourceType="Script" sortIndex="3" path="JoTags/Vendor/tag-it.min.js" />
       <add name="JoTagsVendor" resourceType="Style" sortIndex="4" path="JoTags/Vendor/jquery-ui.min.css" />
       <add name="JoTagsVendor" resourceType="Style" sortIndex="5" path="JoTags/Vendor/jquery.tagit.css" />
       <add name="JoTagsVendor" resourceType="Style" sortIndex="6" path="JoTags/Vendor/tagit.ui-zendesk.css" />
      </clientResources>

      <clientModule>
          <moduleDependencies>
              <add dependency="CMS" type="RunAfter" />
          </moduleDependencies>
          <requiredResources>
              <add name="JoTagsVendor" />
              <add name="JoTags" />
          </requiredResources>
      </clientModule>
</module>  

The "add assembly" part is the name of your project, in my case, "PlayTime". Then you will register your Dojo module and set a correct path. The path is relative to the ClientResources folder.
If that folder doesn't exists in your project root, create it.
Here's an image of my folder structure:

The javascript(Dojo code) is located in the Scripts/Editors folder. It's named JoTagsProperty.js. I placed it there because all of my other custom properties had their editor files placed there, you can place your wherever you want as long as the path(ClientEditingClass) in the EditorDescriptor class is correct :)

Dojo time!

JoTagsProperty.js

define([  
    "dojo/_base/declare",
    "dojo/_base/array",
    "dijit/form/TextBox"
 ],
 function (
    declare,
    array,
    TextBox) {
    return declare([TextBox], {
        postCreate: function () {
            var that = this;
            var input = $(this.domNode).find('input[name="tags"]');
            $.getJSON("/jotags", function (data) {
                that._setUpTagIt(input, data);
            });
    },

    _setUpTagIt: function (input, tags) {
        var that = this;
        $(input).tagit({
            availableTags: tags,
            allowSpaces: true,
            removeConfirmation: true,
            beforeTagAdded: function (evt, ui) {
                if (ui.duringInitialization) return;

                var userInputedTag = ui.tagLabel.toLowerCase();
                var tagAllowed = that._isTagAllowed(userInputedTag, tags);

                if (!tagAllowed) {
                    return false;
                }
            }
        });
    },

    _isTagAllowed: function (wantedTag, tags) {
        var exists = array.indexOf(tags, wantedTag);
        if (exists > -1) {
            return true;
        }
        return false;
    }
    });
});

You can read more about what methods Dojo uses here.

The postCreate function runs when the widget has been rendered and you can also access the DOM node here.

My property works like this:

  1. Get the input DOM element
  2. Fetch the available tags from my backend
  3. Initialize TagIt on the input field
  4. Profit!

The isTagAllowed function checks if the user has entered a tag that's allowed, if not, it will be removed.