Setting an editor prefix for an editor shape (having multiple/nested editors)

Topics: Core
Developer
Aug 21, 2012 at 11:01 PM

Is there a way to have multiple editors (built with ContentManager.BuildEditor()) on the same page, for multiple content items? Well, displaying them is no problem :-), but updating is. Normally I'd use collections for model binding but since how the input elements are named is not under my control I don't know what to do.

Shape.Metadata.Prefix apparently does nothing.

Anybody with an idea? Should this be an extension to BuildEditor()?

Developer
Aug 21, 2012 at 11:29 PM
Edited Aug 21, 2012 at 11:30 PM

I think it would be nice if the BuildEditor would be overloaded taking an BuildEditorArgs object, which contains a "RootPrefix" property. You could then pass in a prefix for each iteration, e.g. "editor[0]", "editor[1]". The resulting input elements would then have names like "editor[0].TitlePart.Title", "editor[1].TitlePart.Title".

Developer
Aug 22, 2012 at 10:38 AM

Apparently you had the same idea about BulidDisplay(): http://orchard.codeplex.com/discussions/346582

I experimented around yesterday, couldn't get it to work. shape Metadata.Prefix is apparently not used for the editor prefix (I tried to set it from various shape events too), or it's completely overridden by drivers. Anyway, if it would be set it still needed to be somehow used when updating, where again drivers use their own prefixes; however, from a custom controller the IUpdateModel implementation could add e.g. a prefix to the prefix, so this might work...

Developer
Aug 22, 2012 at 8:44 PM

I've opened an issue for this: http://orchard.codeplex.com/workitem/18970

Developer
Aug 24, 2012 at 12:23 PM

A possible solution I've developed at Onestop:

// PrefixedEditorManager for building prefix-aware shapes and updating them

public interface IPrefixedEditorManager : IDependency {
        IEnumerable<int> ItemIds { get; }
        dynamic BuildShape(IContent content, Func<IContent, dynamic> shapeFactory);
        dynamic UpdateEditor(IContent content, IUpdateModel updater, string groupId = "");
    }

    public class PrefixedEditorManager : IPrefixedEditorManager {
        private readonly IContentManager _contentManager;
        private readonly HashSet<int> _itemIds = new HashSet<int>();

        IEnumerable<int> IPrefixedEditorManager.ItemIds {
            get { return _itemIds; }
        }

        public PrefixedEditorManager(IContentManager contentManager) {
            _contentManager = contentManager;
        }

        public dynamic BuildShape(IContent content, Func<IContent, dynamic> shapeFactory) {
            _itemIds.Add(content.ContentItem.Id);
            return shapeFactory(content);
        }

        public dynamic UpdateEditor(IContent content, IUpdateModel updater, string groupId = "") {
            return _contentManager.UpdateEditor(content, new PrefixedUpdater(updater, content.ContentItem.Id), groupId);
        }

        public static string AttachPrefixToPrefix(int itemId, string currentPrefix) {
            return "id-" + itemId + "_" + currentPrefix;
        }

        private class PrefixedUpdater : IUpdateModel {
            private readonly IUpdateModel _updater;
            private readonly int _itemId;

            public PrefixedUpdater(IUpdateModel updater, int itemId) {
                _updater = updater;
                _itemId = itemId;
            }

            bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
                return _updater.TryUpdateModel<TModel>(model, AttachPrefixToPrefix(_itemId, prefix), includeProperties, excludeProperties);
            }

            void IUpdateModel.AddModelError(string key, LocalizedString errorMessage) {
                _updater.AddModelError(key, errorMessage);
            }
        }
    }


// Corresponding ShapeTableProvider to set the prefix for editors built through the previous service
    public class PrefixedEditorShapeTable : IShapeTableProvider {
        private readonly Work<IPrefixedEditorManager> _prefixedEditorManagerWork;

        public PrefixedEditorShapeTable(Work<IPrefixedEditorManager> prefixedEditorManagerWork) {
            _prefixedEditorManagerWork = prefixedEditorManagerWork;
        }

        public void Discover(ShapeTableBuilder builder) {
            builder.Describe("EditorTemplate")
                .OnDisplaying(displaying => {
                    var shape = displaying.Shape;
                    int itemId = shape.ContentItem.Id;

                    if (!_prefixedEditorManagerWork.Value.ItemIds.Contains(itemId)) return;

                    shape.Prefix = PrefixedEditorManager.AttachPrefixToPrefix(itemId, shape.Prefix);
                });
        }
    }

I'd push this to the core if appropriate. What do you think?

Developer
Aug 25, 2012 at 3:29 AM

I think it's very creative! Could you provide some sample code on how to use this exactly? Both for rendering and handling the post back.

Developer
Aug 26, 2012 at 11:53 AM

Thank you! Sure, here it is:

// Building shapes
// _prefixedEditorManager is an injected IPrefixedEditorManager instance     

foreach (var item in contentItems) {
    // Inside the shape factory lambda we could use BuildEditor or any other shape building mechanism too
    var editorShape = _prefixedEditorManager.BuildShape(item, (content => _contentManager.BuildDisplay(content)));
    // Do something with the shape, e.g. add it to a list shape
}


// Updating items, same as ContentManager.UpdateEditor
// We could get the list of items by using the same technique as Core.Contents AdminController bulk edit: include a form element with name "itemIds" for each item, then let the model binder give us all ids by having an IEnumerable<int> itemIds argument on the post back action.
foreach (var item in items) {
    _prefixedEditorManager.UpdateEditor(item, this);
}

Developer
Aug 28, 2012 at 10:16 PM

I love it. Thanks.

Developer
Aug 28, 2012 at 10:28 PM

Great, I'm glad :-).

Jan 11, 2013 at 11:27 AM

awesome Piedone, thanks !

Developer
Jan 11, 2013 at 11:57 AM

I'm glad :-).

Developer
Sep 9, 2013 at 9:21 PM
Thanks to the kindness of Onestop, PrefixedEditorManager is now part of Helpful Libraries.