A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

ima9ines
ima9ines  -  CMS
Aug 3, 2017
  3132
(0 votes)

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.

action links displayed when not over max size of limited items

The action links section in ContentArea is hidden when total items inside that ContentArea is 5.

hide action links in ContentArea on-page editing mode

On-Page Editing mode with popup

The action links section in ContentArea is hidden when total items inside that ContentArea are 5

hide action links in popup on-page editing mode

All Properties mode

The action links section in ContentArea is hidden when total items inside that ContentArea are 5

hide action links in ContentArea all-properties mode

Related topics

Aug 03, 2017

Comments

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP: Learning Optimizely Just Got Easier: Introducing the Optimizely Learning Centre

On the back of my last post about the Opti Graph Learning Centre, I am now happy to announce a revamped interactive learning platform that makes...

Graham Carr | Jan 31, 2026

Scheduled job for deleting content types and all related content

In my previous blog post which was about getting an overview of your sites content https://world.optimizely.com/blogs/Per-Nergard/Dates/2026/1/sche...

Per Nergård (MVP) | Jan 30, 2026

Working With Applications in Optimizely CMS 13

💡 Note:  The following content has been written based on Optimizely CMS 13 Preview 2 and may not accurately reflect the final release version. As...

Mark Stott | Jan 30, 2026

Experimentation at Speed Using Optimizely Opal and Web Experimentation

If you are working in experimentation, you will know that speed matters. The quicker you can go from idea to implementation, the faster you can...

Minesh Shah (Netcel) | Jan 30, 2026