PerplexDigital / Perplex.ContentBlocks

Block based content editor for Umbraco
MIT License
31 stars 15 forks source link

Is there a way to set the display/sort order of the Content Blocks in the backoffice modal selection? #83

Closed GreyGhostStudio closed 2 months ago

GreyGhostStudio commented 8 months ago

I've broken out the content blocks into folders for code organization. Each folder contains the razor view(s) and a class file for the content block definition scaffolding.

Hero (Folder)

When doing it this way, the content blocks appear in descending alphabetic order (Z-A) in the Content Block modal selection window. Can the overall sort order be set within each category? I have the feeling I'm totally missing something.

PerplexDaniel commented 8 months ago

We also break out definitions in seperate files, and as long as you add them in the order you desire to the IContentBlocksRepository they will appear in that order in the backoffice UI.

To elaborate a bit further we use a (custom) interface:

public interface IContentBlockDefinitionProvider
{
    IContentBlockDefinition Definition { get; } 
    int SortOrder { get; }
}

Which is used for example in ContentBlockImageDefinitionProvider which generates the definition of our Image ContentBlock. Ensure to register this provider in your DI container.

public class ContentBlockImageDefinitionProvider : IContentBlockDefinitionProvider
{
    public IContentBlockDefinition Definition { get; } = new ContentBlockDefinition
    {
        Id = ...
        Name = "Image",
        // Other properties      
    };

    public int SortOrder { get; } = 40;
}

Then, in a ContentBlocksComponent.cs just add them in order to the repository:

public class ContentBlocksComponent : IComponent
{
    private readonly IEnumerable<IContentBlockDefinitionProvider> _providers;
    private readonly IContentBlockDefinitionRepository _repository;

    public ContentBlocksComponent(
        IEnumerable<IContentBlockDefinitionProvider> providers,
        IContentBlockDefinitionRepository repository)
    {
        _providers = providers;
        _repository = repository;
    }

    public void Initialize()
    {
        RegisterDefinitions();
    }

    private void RegisterDefinitions()
    {
        var definitions = _providers
            .OrderBy(p => p.SortOrder)
            .ThenBy(p => p.Definition.Name)
            .Select(p => p.Definition)
            .ToArray();

        foreach (var definition in definitions)
        {
            _repository.Add(definition);
        }
    }
}

This works fine for us at Perplex but it was probably better to provide a native mechanism in the package to order blocks when you call IContentBlockDefinitionRepository.Add from various places in various orders.

GreyGhostStudio commented 8 months ago

Offering a native mechanism would be great, but having it default/fallback to simple alphabetic (a-z) would be enough for our needs. Big thanks for the insight, code snippets, and work on this project.

PerplexDaniel commented 8 months ago

We will discuss adding a simple way to configure the default sorting to be done on definition.Name, that sounds reasonable but the out of the box default will likely be unchanged since it's been the same (undefined / insertion order) since v1 so changing this will suddenly change the order on many sites. Not sure when we will add this however.

In the meantime though, it is very easy to modify this yourself, just copy the code below into a new file in your solution. This will sort all block alphabetically on definition.Name by replacing the IContentBlockDefinitionRepository with a custom version. This was tested in Umbraco v10 and ContentBlocks v2 but should also work for other Umbraco versions and for ContentBlocks v3.

public class AlphabeticallySortedDefinitionRepository : IContentBlockDefinitionRepository
{
    private readonly IContentBlockDefinitionRepository _proxy;
    private readonly IContentBlockDefinitionFilterer _filterer;

    public AlphabeticallySortedDefinitionRepository(IContentBlockDefinitionFilterer filterer)
    {
        _proxy = new InMemoryContentBlockDefinitionRepository(filterer); // <- this is the built-in repository
        _filterer = filterer;
    }

    public IEnumerable<IContentBlockDefinition> GetAll()
    {
        // Any custom sorting logic can be applied here.
        return _proxy.GetAll().OrderBy(d => d.Name);
    }

    public IEnumerable<IContentBlockDefinition> GetAllForPage(int pageId, string culture) => _filterer.FilterForPage(GetAll(), pageId, culture);
    public IEnumerable<IContentBlockDefinition> GetAllForPage(string documentType, string culture) => _filterer.FilterForPage(GetAll(), documentType, culture);

    public void Add(IContentBlockDefinition definition) => _proxy.Add(definition);
    public IContentBlockDefinition GetById(Guid id) => _proxy.GetById(id);
    public void Remove(Guid id) => _proxy.Remove(id);
}

[ComposeAfter(typeof(ContentBlockDefinitionComposer))]
public class AlphabeticallySortedDefinitionRepositoryComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder) =>
        builder.AddUniqueService<IContentBlockDefinitionRepository, AlphabeticallySortedDefinitionRepository>();
}
PerplexDaniel commented 2 months ago

Closing this one since a solution was posted. If we add some sort of configuration option for this I will update this issue.