ima9ines
ima9ines  -  CMS
Aug 3, 2017
  8631
(5 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

Aug 3, 2017 01:14 PM

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

ima9ines
ima9ines Aug 3, 2017 02:21 PM

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!

valdis
valdis Aug 3, 2017 03:05 PM

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

Aug 10, 2017 12:16 PM

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

Deepa Puranik
Deepa Puranik Feb 11, 2021 04:20 PM

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.

Please login to comment.
Latest blogs
Caching & Rendering of metadata from the DAM

For the EPiServer.Cms.WelcomeIntegration.UI 1.2.0 release, we have facilitated the ability to cache and render metadata from assets within the DAM....

Matthew Slim | Jun 2, 2023

Conversion Rate Optimization: Maximizing ROI through Data-Driven Decisions

In today's digital landscape, businesses are constantly looking for ways to improve their online presence and drive more conversions. One effective...

JessWade | Jun 1, 2023

Enhance Your Writing with Suggestions

Are you tired of staring at a blank screen, struggling to find the right words? The Epicweb.Optimizely.AIAssistant Addon is here to revolutionize...

Luc Gosso (MVP) | May 31, 2023 | Syndicated blog

Content Graph - Letting GraphQL do all the hard work for you

Background As we have seen before, setting up Content Graph on the CMS side is pretty easy. However, when it comes to the “head” part of the setup,...

Kunal Shetye | May 26, 2023 | Syndicated blog