Calling all developers! We invite you to provide your input on Feature Experimentation by completing this brief survey.

 

ima9ines
ima9ines  -  CMS
Aug 3, 2017
  10042
(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
Find and delete non used media and blocks

On my new quest to play around with Blazor and MudBlazor I'm going back memory lane and porting some previously plugins. So this time up is my plug...

Per Nergård (MVP) | Jan 21, 2025

Optimizely Content Graph on mobile application

CG everywhere! I pull schema from our default index https://cg.optimizely.com/app/graphiql?auth=eBrGunULiC5TziTCtiOLEmov2LijBf30obh0KmhcBlyTktGZ in...

Cuong Nguyen Dinh | Jan 20, 2025

Image Analyzer with AI Assistant for Optimizely

The Smart Image Analyzer is a new feature in the Epicweb AI Assistant for Optimizely CMS that automates the management of image metadata, such as...

Luc Gosso (MVP) | Jan 16, 2025 | Syndicated blog

How to: create Decimal metafield with custom precision

If you are using catalog system, the way of creating metafields are easy – in fact, you can forget about “metafields”, all you should be using is t...

Quan Mai | Jan 16, 2025 | Syndicated blog

Level Up with Optimizely's Newly Relaunched Certifications!

We're thrilled to announce the relaunch of our Optimizely Certifications—designed to help partners, customers, and developers redefine what it mean...

Satata Satez | Jan 14, 2025

Introducing AI Assistance for DBLocalizationProvider

The LocalizationProvider for Optimizely has long been a powerful tool for enhancing the localization capabilities of Optimizely CMS. Designed to ma...

Luc Gosso (MVP) | Jan 14, 2025 | Syndicated blog