Restricting the allowed types in a content area
A feature that was sorely missed in EPiServer 7 was the ability to restrict the type of content that could be dragged and dropped into a content area. Well, you will all be glad to know that in EPiServer 7.5 the content area has received a lot of loving and it is now possible to restrict the content types that can be dragged and dropped into it.
Something else that is also worth mentioning is the fact that media is also IContent now. So you can say goodbye to those silly ImageBlock and VideoBlock implementations that I know you all have, because this change means that it is possible to drag and drop image or video content directly into the content area.
These two changes open up a world of use cases for the content area. For example, image galleries or image carousels where the user can simply drag an image into the content area and it is added to the gallery or carousel. Another use case might be that you want to restrict which blocks can be added to a content area in the sidebar or footer or on the home page.
The Code
The edit interface is built up based on metadata for each property. This metadata includes the editor widget to use and also any default configuration that the widget should be initialized with. This metadata can be customized via an editor descriptor.
[EditorDescriptorRegistration(TargetType = typeof(ContentArea), UIHint = "Gallery")]
public class ImageGalleryEditorDescriptor : EditorDescriptor
{
public ImageGalleryEditorDescriptor()
{
// Setup the types that are allowed to be dragged and dropped into the content
// area; in this case only images are allowed to be added.
AllowedTypes = new Type[] { typeof(IContentImage) };
// Unfortunetly the ContentAreaEditorDescriptor is located in the CMS module
// and thus can not be inherited from; these settings are copied from that
// descriptor. These settings determine which editor and overlay should be
// used by this property in edit mode.
ClientEditingClass = "epi-cms.contentediting.editors.ContentAreaEditor";
OverlayConfiguration.Add("customType", "epi-cms.widget.overlay.ContentArea");
}
}
Here you can see that we've set allowed types to only include IContentImage types. So any class extending this interface can be added to the content area. This allowed types property automatically populates the relevant overlay and editor widget metadata, so that drag and drop will be restricted to images in both on-page and all properties editing modes.
The other two properties indicate which editor widget should be used and that a custom overlay should also be used. Unfortunately these need to be specified as we can't extend the ContentAreaEditorDescriptor and inherited them. This is a problem we are aware of and in the process of solving.
With that added to the project it is now just a matter of telling the relevant properties to use this editor descriptor rather than the default one. This is done via the UIHint that we added in the editor descriptor registration attribute. The attribute indicates that content area properties with a UIHint of Gallery should use this editor descriptor. So it is just a matter of adding a UIHint to the relevant properties.
[UIHint("Gallery")]
public virtual ContentArea Gallery { get; set; }
Caveats
The main thing to note is this only affects drag and drop in edit mode. So it is still possible to add other types of content via code.
The only other caveat that I've seen so far is that the editor and overlay for the content area both have some inbuilt help text that contains an action link for shortcutting to the block creation process. This process if followed through automatically adds the new block to the content area. Currently there is no way to configure this text not to display. It should be a fairly simple fix and there is a feature request for it so hopefully that should come soon.
Cool. Sadly it doesn't seem to work with interfaces
) new Type[1] { typeof (IContentData) };
attributes)
The original ContentArea Descriptor has
this.AllowedTypes = (IEnumerable
But it doesn't work when I try with
this.AllowedTypes = new[] { typeof(IDropContent) };
Is it possible to add a restriction so that only the specific type may be added.
For example If I add StandardPage in the Alloy Templates, I am also available to add ProductPage that is a sub class of StandardPage.
Also the original ContentArea Descriptor has this override:
public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable
{
base.ModifyMetadata(metadata, attributes);
metadata.OverlayConfiguration["customType"] = (object) "epi-cms.widget.overlay.ContentArea";
metadata.CustomEditorSettings["converter"] = (object) "epi-cms.propertycontentarea";
}
Should they also be added in our custom descriptor?
No unfortunately the only interfaces that will work are the base content interfaces. This is because we associate all content in the user interface with a UIDescriptor, and this association is calculated based on the left most branch of the inheritance tree. So if you have
public class MyBlock : BlockData, IDropContent
Then the UIDescriptor for BlockData will be what is chosen.
As for having restrictions, this is something that we talked about during development but didn't have time to implement, so it wasn't included. It is something that we could add as a feature later if there is demand for it.
Then to answer your last question, the metadata.OverlayConfiguration["customType"] is actually included in my example, I just added it in the constructor instead. As for the metadata.CustomEditorSettings["converter"] that is no longer used so can be safely ignored.
You might want to check out this blog post: http://world.episerver.com/Blogs/Linus-Ekstrom/Dates/2013/12/Restriction-of-content-types-in-properties/
In our current solution, we're also using validation, which makes sure the SlideShowArea will only contain SlideBlocks:
Validate(SlideShowPage instance)
();
(x => x.SlideShowArea),
();
public class SlideShowValidator : IValidate
{
public IEnumerable
{
if (instance.SlideShowArea == null)
return Enumerable.Empty
if (instance.SlideShowArea.Items.Any(item => !(item.GetContent() is SlideBlock)))
{
return new[]
{
new ValidationError
{
ErrorMessage = "Slides can be only of Slide Block type",
PropertyName =
ReflectionHelper.GetPropertyName
Severity = ValidationErrorSeverity.Error,
ValidationType = ValidationErrorType.StorageValidation
}
};
}
return Enumerable.Empty
}
}