Limiting items in a ContentArea
Feature
The ContentArea should
- Supported to limits a number of block inside it (for individual ContentArea).
- When total blocks is over max limited size, the action links section should be hidden.
- When total blocks is over max limited size, editor cannot DnD a block to that ContentArea.
Solution
The attribute
By using attribute, we can set individual limited total items for each ContentArea.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class MaxItemCountAttribute : Attribute
{
public int Count { get; }
public MaxItemCountAttribute(int maxItemCount)
{
Count = maxItemCount;
}
}
The editor descriptor
We needed to extend both ContentArea and ContentAreaEditor widgets. Inside the custom editor descriptor, we read the value of attribute and then render it to client side.
/// <summary>
/// Editor descriptor that extends the <see cref="ContentAreaEditorDescriptor"/> for a <see cref="ContentArea"/>
/// that marked with <see cref="MaxItemCountAttribute"/> in order to limits total items inside it.
/// </summary>
[EditorDescriptorRegistration(TargetType = typeof(ContentArea), UIHint = "ContentAreaWithMaxItem")]
public class CustomContentAreaEditorDescriptor : ContentAreaEditorDescriptor
{
public CustomContentAreaEditorDescriptor()
{
ClientEditingClass = "alloy/contentediting/editors/CustomContentAreaEditor";
}
public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
{
const string maxItemCount = "maxItemCount";
var maxItemCountAttribute = metadata.Attributes.OfType<MaxItemCountAttribute>().SingleOrDefault();
if (maxItemCountAttribute != null)
{
metadata.EditorConfiguration[maxItemCount] = maxItemCountAttribute.Count;
metadata.OverlayConfiguration[maxItemCount] = maxItemCountAttribute.Count;
}
base.ModifyMetadata(metadata, attributes);
metadata.OverlayConfiguration["customType"] = "alloy/widget/overlay/CustomContentArea";
}
}
The custom ContentAreaEditor
define([
// dojo
"dojo/_base/declare",
"dojo/dom-style",
// epi
"epi-cms/contentediting/editors/ContentAreaEditor"
], function (
// dojo
declare,
domStyle,
// epi
ContentAreaEditor
) {
return declare([ContentAreaEditor], {
postCreate: function () {
this.inherited(arguments);
this._toggleDisplayActionLinks();
},
onChange: function (value) {
this._toggleDisplayActionLinks();
},
_setValueAttr: function (value) {
this.inherited(arguments);
this._toggleDisplayActionLinks();
},
_toggleDisplayActionLinks: function () {
var display = this._canAddItem();
this.actionsContainer && domStyle.set(this.actionsContainer, "display", display ? "" : "none");
this.tree && this.tree.set("readOnly", !display);
},
_canAddItem: function () {
// tags:
// private
if (this.maxItemCount == undefined) {
return true;
}
return this.maxItemCount > this.model.get("value").length;
}
});
});
The custom ContentArea
define([
// dojo
"dojo/_base/declare",
"dojo/dom-style",
// epi
"epi-cms/widget/overlay/ContentArea"
], function (
// dojo
declare,
domStyle,
// epi
ContentArea
) {
return declare([ContentArea], {
updatePosition: function () {
this.inherited(arguments);
this._toggleDisplayActionLinks();
},
executeAction: function (actionName) {
this._toggleDisplayActionLinks();
if (actionName === "createnewblock" && !this._canAddItem()) {
return;
}
this.inherited(arguments);
},
_onDrop: function (data, source, nodes, isCopy) {
// isCopy:
// Flag indicating whether the drag is a copy. False indicates a move.
// tags:
// protected extension
this._toggleDisplayActionLinks();
if (isCopy && !this._canAddItem()) {
return;
}
this.inherited(arguments);
},
_toggleDisplayActionLinks: function () {
this.textWithLinks && domStyle.set(this.textWithLinks.domNode, "display", this._canAddItem() ? "" : "none");
},
_canAddItem: function () {
// tags:
// private
if (this.maxItemCount == undefined) {
return true;
}
return this.maxItemCount > this.getChildren().length;
}
});
});
Start page
Add following attributes
[UIHint("ContentAreaWithMaxItem")]
[MaxItemCount(5)]
to MainContentArea property
[Display(
GroupName = SystemTabNames.Content,
Order = 320)]
[CultureSpecific]
[UIHint("ContentAreaWithMaxItem")]
[MaxItemCount(5)]
public virtual ContentArea MainContentArea { get; set; }
Result
On-Page Editing mode
When total items inside the ContentArea is 4 (qualified with the max item count), action links section still displayed.
The action links section in ContentArea is hidden when total items inside that ContentArea is 5.
On-Page Editing mode with popup
The action links section in ContentArea is hidden when total items inside that ContentArea are 5
All Properties mode
The action links section in ContentArea is hidden when total items inside that ContentArea are 5
Hi Tuan,
I like your idea of solving this, but I found two issues with this solution:
1. Content area should not be set to read-only mode when it reaches the max items count limit. Editors should be able to change the sort order of content area items.
2. When the limit is reached (5 or more items), updatePosition method (CustomContentArea) goes into an infinite loop. (when this.inherited(arguments); is called after this._toggleDisplayActionLinks();)
Hi Dejan Caric,
Thanks for your feedback.
interesting idea. this goes together a bit with server side validation for bootstrap styles - where count of items matter if you want to maintain row and widths of the items.. thx for sharing
Thanks for sharing the code Tuan!
I would personally implement a custom attribute that inherits ValidationAttribute. This way you do not have to change the custom editor, and the editor will get a warning when the maximum number of items have been passed. In this solution you are inheriting a few private methods in the base editor, meaning that this could break in any Episerver upgrade, including non-major ones, and this should always be avoided when possible.
https://world.episerver.com/documentation/Items/Developers-Guide/Episerver-CMS/9/Content/Properties/Property-types/Writing-custom-attributes/
Regards
Linus
Hello,
Is this approach works with latest version of EPiServer CMS version 11.20.3.0, we are getting error in cms when I add scripts.