Views: 1491
Number of votes: 4
Average rating:

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

    (By Dejan Caric , 03 August 2017 13:14, Permanent link)

    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();)

    (By Tuan Do , 03 August 2017 14:21, Permanent link)

    Hi Dejan Caric,

    Thanks for your feedback.

    1. As I checked, I still able to change the sort order of content area items. Except in the popup, I cannot DnD an item to re-order it, but I can use context menu to move it up/down (I will find a better solution later).
    2. I updated the code to fix that issue. Thanks!
    (By valdis iljuconoks , 03 August 2017 15:05, Permanent link)

    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

    (By Linus Ekström , 10 August 2017 12:16, Permanent link)

    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

  Please login to post a comment