Linus Ekström
Jul 4, 2012
  16273
(5 votes)

Creating a custom editor in EPiServer 7

EPiServer 7 comes with a new user interface. It’s built as a client side application using JavaScript and is based on the client side framework Dojo. This affects editing of objects, for instance pages, and in EPiServer 7 we introduce a new way to create custom editors. Since the entire application is based on the Dojo framework, editing of content is done using the Dijit widget system which is Dojos widget system layered on top of the Dojo core.

It’s worth mentioning that we have a legacy editor that wraps any custom editors written for EPiServer 5-6 inside an iframe in a pop-up so any custom editors you have written will still work, although they require an additional click to edit.

General goals with the new editing system

Before going into the details of how to create a custom editor I would like to state few of the goals when developing the new object editing system:

  1. Make a generic editing system that can be reused for all EPiServer products and functions.It's currently only used when editing pages and shared blocks, but the functionality has been written to be able to edit any .NET object. In the future this can be used to edit dynamic content, visitor groups etc. so that you as a developer don't need to learn several ways of creating editors.
  2. Reduce the need to write custom properties. We have added support to use both standard .NET data annotation attributes as well as some custom attributes, making it possible to annotate the model for some cases instead of writing custom properties. See David Knipes excellent blog post about this.
  3. Add global registration for type editors. You should only need to define that your type has a specific editor once in the system. That editor would then be used everywhere we use the object editing system.

Connecting an editor to a type

In the EPiServer CMS 5-6 property system you connected a property editor by creating an instance of PropertyDataControl in your property. This instance was then responsible for creating controls both for displaying content on the site as well as creating an editor for the property.

In EPiServer 7 we have strived to separate the user interface logic and the rendering of content from the actual data types. To register an editor for a type you need to connect an editor descriptor to your property. There are several ways to do this. Either you annotate your property with your desired editor class in Dojo:

   1: [ClientEditor(ClientEditingClass = "app.editors.EmailTextbox")]
   2: public virtual string EmailAddress { get; set; }

You can also connect the property to an editor descriptor that is responsible for defining the editor class as well as additional settings for the editor:

   1: [EditorDescriptor(EditorDescriptorType = typeof(ImageUrlEditorDescriptor))]
   2: public virtual string Logotype2 { get; set; }

The two examples above will have effect on the property only. You can also register global editor descriptors and connect them to one or several types, effectively decoupling the UI-logic from the model:

   1: [EditorDescriptorRegistration(TargetType = typeof(Email))]
   2: public class PageListEditorDescriptor : EditorDescriptor
   3: {
   4:     public PageListEditorDescriptor()
   5:     {
   6:         this.ClientEditingClass = "app.editors.EmailEditor";
   7:     }
   8: }

Note: In the example above I connect the editor to my class Email which is just a regular .NET class used as my value type for my property. In the EPiServer 7 preview you need to connect your editor descriptor to your property type, for instance PropertyEmail and not the value type Email.

Creating the actual editor

The following example shows how an editor with e-mail address validation can look like.

   1: define([
   2:     "dojo",
   3:     "dojo/_base/declare",
   4:     "dijit/_Widget",
   5:     "dijit/_TemplatedMixin"
   6: ], function (
   7:     dojo,
   8:     declare,
   9:     _Widget, 
  10:     _TemplatedMixin) {
  11:  
  12:     declare("app.editors.EmailTextbox", [_Widget, _TemplatedMixin], {
  13:  
  14:         // templateString: [protected] String
  15:         //    A string that represents the default widget template.
  16:         templateString: '<div> \
  17:                          <input type="email" data-dojo-attach-point="email" data-dojo-attach-event="onchange:_onChange" /> \
  18:                         </div>',
  19:  
  20:         postCreate: function () {
  21:             // summary:
  22:             //    Set the value to the textbox after the DOM fragment is created.
  23:             // tags:
  24:             //    protected
  25:  
  26:             this.set('value', this.value);
  27:  
  28:             if (this.intermediateChanges) {
  29:                 this.connect(this.email, 'onkeydown', this._onIntermediateChange);
  30:                 this.connect(this.email, 'onkeyup', this._onIntermediateChange);
  31:             }
  32:         },
  33:  
  34:         focus: function () {
  35:             // summary:
  36:             //    Put focus on this widget.
  37:             // tags:
  38:             //    public
  39:  
  40:             dijit.focus(this.email);
  41:         },
  42:  
  43:         isValid: function () {
  44:             // summary:
  45:             //    Indicates whether the current value is valid.
  46:             // tags:
  47:             //    public
  48:  
  49:             var emailRegex = '[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+';
  50:             if (!this.required) {
  51:                 emailRegex = '(' + emailRegex + ')?';
  52:             }
  53:             var regex = new RegExp('^' + emailRegex + '$');
  54:             return regex.test(this.value);
  55:         },
  56:  
  57:         onChange: function (value) {
  58:             // summary:
  59:             //    Called when the value in the widget changes.
  60:             // tags:
  61:             //    public callback
  62:         },
  63:  
  64:         _onIntermediateChange: function (event) {
  65:             // summary:
  66:             //    Handles the textbox key press events event and populates this to the onChange method.
  67:             // tags:
  68:             //    private
  69:  
  70:             if (this.intermediateChanges) {
  71:                 this._set('value', event.target.value);
  72:                 this.onChange(this.value);
  73:             }
  74:         },
  75:  
  76:         _onChange: function (event) {
  77:             // summary:
  78:             //    Handles the textbox change event and populates this to the onChange method.
  79:             // tags:
  80:             //    private
  81:  
  82:             this._set('value', event.target.value);
  83:             this.onChange(this.value);
  84:         },
  85:  
  86:         _setValueAttr: function (value) {
  87:             // summary:
  88:             //    Sets the value of the widget to "value" and updates the value displayed in the textbox.
  89:             // tags:
  90:             //    private
  91:  
  92:             this._set('value', value);
  93:             this.email.value = this.value || '';
  94:         }
  95:     });
  96: });

EPiServer configures a default namespace in Dojo called “app” which is mapped to the folder “ClientResources\Scripts”. In this case we have placed the editor in the “ClientResources\Scripts\Editors” folder in the site so that we can load the script file whenever the class "app.editors.EmailTextbox" is required. The result will look like this:

It’s also worth mentioning that the easiest way to accomplish email validation is to add a RegularExpression validaton attribute to your property which removes the need for a custom editor.

Technorati Tags: ,,
Jul 04, 2012

Comments

Jul 4, 2012 02:34 PM

While the SDK documentation is great, this is just what I needed.

Thanks, Linus!

Jul 25, 2012 05:02 PM

thanks for this

been massive help on a current project

John Frings
John Frings Oct 24, 2012 11:35 AM

Hello Linus, I added the simplest possible editor and added it to the page type like this:

[ClientEditor(ClientEditingClass = "app.editors.FooBar")]
[Display(Name = "Name of foobar", GroupName = SystemTabNames.Content)]
public virtual string FooBar { get; set; }

When editing the page the editor is loaded almost as expected; instead of displaying "Name of foobar" on the left hand column, the following is shown: "[object HTMLLabelElement]".
I'm a little bit confused about where you set the "Email Address" label in your example code. What am I missing?

Oct 24, 2012 04:12 PM

John: I just tried your code on the latest build and it works fine. If you have access to the CTP-forum, please test with the latest build there: otherwise there should be a pubic build out pretty soon that you can use.

kiran.hegde@meritglobe.com
kiran.hegde@meritglobe.com Dec 29, 2012 10:00 AM

Hi Linus,
We have a lot of custom properties written in the earlier versions, some of them I managed to convert to the DOJO based properties.
But some of them have complex design and takes long time to learn and migrate to DOJO. Is it possible to hook the legacy editor for these properties?
I have seen in one of your blog that, we can use IFrame to keep the legacy editors, but I am not sure whether we can use this for the Custom properties.
If we can use legacy editors how can we do that?

Dec 30, 2012 01:57 PM

Hi Kiran!

I understand your concern and sure, you can use the legacy system for your properties that you don't want/have time to convert. The legacy system is used automatically whenever there is no registered editor descriptor for a given type/uiHint-combination. If you are using a well known type, like string, int etc., you need to add an uiHint to make sure that you get a type/uiHint-combination without any editor descriptors. For instance

[UIHint("somethinguinique")]
public virtual sting MyCustomProperty

kayvan shaabani
kayvan shaabani Jan 7, 2013 10:57 AM

Thanks Linus

I have a simple requirement which I'm not sure if I need (if its possible) to create a new editor using Dojo.

The requirement is to have a property of type "PageReference" which should be set by only pages of a specific page type (i.e. AuthorPageType).

I need to retain the Episerver's nice default PageRefrence editor, but just filter the pages in tree view based on specific ones, so the user only select the right ones.

Is this requirement something somewhere in the SDK which I cannot see or I need to do extra work?

Thanks
Kayvan

Jan 18, 2013 11:35 AM

@Kayvan. Sorry for the late answer. There is no support for disabling certain items when selecting pages in the tree. If you want to use a tree I would try to add a custom validation attribute that you put on the property in the model. Check this blog post:

http://www.menzies-smith.co.uk/?p=76

The other option you have is to show possible selections in a drop down. If there are only a few options I would go for Joel Abrahamssons solution:

http://joelabrahamsson.com/entry/limiting-content-and-page-reference-properties-to-values-of-a-specific-type-in-episerver-cms

And if you potentially have a lot of options check this blog post:

http://world.episerver.com/Blogs/Linus-Ekstrom/Dates/2012/11/Creating-a-more-advanced-property-editor/

Greg B
Greg B Nov 12, 2013 05:23 PM

Hi Linus,

I'm having some real trouble getting this working. See here: http://stackoverflow.com/questions/19934429/error-when-trying-to-load-custom-client-editor . Any help appreciated.

Thanks,
Greg

Please login to comment.
Latest blogs
Copy Optimizely SaaS CMS Settings to ENV Format Via Bookmarklet

Do you work with multiple Optimizely SaaS CMS instances? Use a bookmarklet to automatically copy them to your clipboard, ready to paste into your e...

Daniel Isaacs | Dec 22, 2024 | Syndicated blog

Increase timeout for long running SQL queries using SQL addon

Learn how to increase the timeout for long running SQL queries using the SQL addon.

Tomas Hensrud Gulla | Dec 20, 2024 | Syndicated blog

Overriding the help text for the Name property in Optimizely CMS

I recently received a question about how to override the Help text for the built-in Name property in Optimizely CMS, so I decided to document my...

Tomas Hensrud Gulla | Dec 20, 2024 | Syndicated blog

Resize Images on the Fly with Optimizely DXP's New CDN Feature

With the latest release, you can now resize images on demand using the Content Delivery Network (CDN). This means no more storing multiple versions...

Satata Satez | Dec 19, 2024

Simplify Optimizely CMS Configuration with JSON Schema

Optimizely CMS is a powerful and versatile platform for content management, offering extensive configuration options that allow developers to...

Hieu Nguyen | Dec 19, 2024

Useful Optimizely CMS Web Components

A list of useful Optimizely CMS components that can be used in add-ons.

Bartosz Sekula | Dec 18, 2024 | Syndicated blog