SelectMany property, but detached from the entity

Vote:
 

Given the CMS page, there is a need to have a checkbox property (let's call it X) whose state is detached from the original object.

Just to make it more clear, here is an example:

  • In Commerce, there is a node type of Category that contains CMS pages (in ContentArea).
  • On the CMS page, I would like to have a property X that displays all categories in Commerce.
    • When the user selects Category then the CMS page is added to the ContentArea of that Category.
    • When the user deselects the Category then the CMS page is removed from the ContentArea of that Category.

The solution I think would be:

  • Have a string property X with SelectMany attribute; load all Categories in ISelectionFactory.
  • Hook to the Published event - check for the current state and previous version (if exists) - remove and/or add according to the differences.

All the above are clear to me and I have an implementation of that. However, I'm not quite sure how to manually check/uncheck checkboxes on property X loading in CMS UI:

  1. Mark checkboxes manually in the SelectionFactory — is that possible?
  2. Or, should I maintain and synchronize the state of the X property after the Category in Commerce is updated? That sounds nightmarish, to be honest, because we need to synchronize these properties both ways.
  3. Or, is there any other way to achieve that?

Using CMS 12.17.x

Thank you :)

#300843
Apr 27, 2023 20:50
Vote:
 

Hi Dariusz

#1

Is property X the one decorated with SelectMany attribute? If yes, you can manually update the value in this property. It just holds comma delimited string values. To make it clear, I'll use the following code snippet to explain further more

I have 3 languages, and if I select first two languages, the value stored in MultipleLanguage will be "EN,SW". In your publish event hook, you can override this value to "PF", and check your CMS, the UI should be updated. 

#2 I don't have much background of your business. As far as the value of category is not changed, everything will be working fine. When categories get deleted, that's where the tricky bits happened. Let's continue with the sample below. 

I have the first two languages selected, and add each language triggers adding a block in a content area. Now first language gets deleted (simulate to your category delete scenario), the UI in CMS won't break but English option will disappear, and value stored in MultipleLanguage remains the same. How would you remove the block added to the content area? So technically you'll need to maintain the state.

I hope above helps

      [SelectMany(SelectionFactoryType = typeof(LanguageSelectionFactory))]
        public virtual string MultipleLanguage { get; set; }

    public class LanguageSelectionFactory : ISelectionFactory
    {
        public IEnumerable<ISelectItem> GetSelections(ExtendedMetadata metadata)
        {
            return new ISelectItem[] { new SelectItem() { Text = "English", Value = "EN" }, new SelectItem() { Text = "Swahili", Value = "SW" }, new SelectItem() { Text = "French Polonesia", Value = "PF" }
    }; } }
#301053
May 02, 2023 8:06
Vote:
 

Hi Dariusz!

If I understand correcty you want to set the value of a property when the content is loaded "manually check/uncheck checkboxes on property X loading in CMS UI:"? If thats the case this pseduo code may help (the example I use simply adds a "A" to the page name on any page but you can change the checkbox values here):

using EPiServer.Framework;
using EPiServer.Framework.Initialization;

namespace YourProjectNamespace
{
    [InitializableModule]
    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    public class ChangePropertyOnLoadInit : IInitializableModule
    {
        private IContentLoader _contentLoader;
        private IHttpContextAccessor _httpContextAccessor;

        public void Initialize(InitializationEngine context)
        {
            _contentLoader = context.Locate.Advanced.GetInstance<IContentLoader>();
            _httpContextAccessor = context.Locate.Advanced.GetInstance<IHttpContextAccessor>();
            var events = context.Locate.Advanced.GetInstance<IContentEvents>();
            events.LoadedContent += Events_LoadedContent;
        }

        private void Events_LoadedContent(object sender, ContentEventArgs e)
        {
            // Only apply for the page type needed
            if (e.Content is not PageData pageData)
            {
                return;
            }

            // Prevent the more than one execution within the same request
            if (_httpContextAccessor.HttpContext?.Items["Skip" + e.ContentLink.ID] != null)
            {
                return;
            }
            _httpContextAccessor.HttpContext?.Items.Add("Skip" + e.ContentLink.ID, true);

            var updateContent = pageData.CreateWritableClone();
            updateContent.PageName += "A";
            e.Content = updateContent;
        }

        public void Uninitialize(InitializationEngine context)
        {
            var events = context.Locate.Advanced.GetInstance<IContentEvents>();
            events.LoadedContent -= Events_LoadedContent;
        }
    }
}

Bear in mind this code runs every time the content is loaded so will called when the page is displayed on the front end. So keep performance in mind with any code you write!

Let me know how you get on. 

#301166
May 04, 2023 10:19
Vote:
 

Thanks for the answers.

Finally, the solution is similar to the answers, and looks like:

  • there is an event subscription - _contentEvents.PublishedContent += OnPublishedContentEventHandler;
  • when there are changes then another page is changed
#301521
May 11, 2023 13:42
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.