OrchardCMS / OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
https://orchardcore.net
BSD 3-Clause "New" or "Revised" License
7.43k stars 2.4k forks source link

Widget Wrapper to work with Flow Part and Layers #9031

Open kdubious opened 3 years ago

kdubious commented 3 years ago

Currently, we have a workaround where we add 2 Alternates to Widget.cshtml to allow us to override a custom widget template, yet still wrap the widget with this: <div class="@String.Join(" ", Model.Classes.ToArray())">

@using OrchardCore.Mvc.Utilities
@using OrchardCore.ContentManagement

@{ var contentItem = (ContentItem)Model.ContentItem;
    var contentTypeClassName = contentItem.ContentType.HtmlClassify();
    Model.Metadata.Alternates.Add("MyWidget__Default");
    Model.Metadata.Alternates.Add($"MyWidget__{Model.ContentItem.ContentType}"); }
<div class="@String.Join(" ", Model.Classes.ToArray())">
    @await DisplayAsync(Model)
</div>

Let's work out how to create a wrapper for widgets in a Flow or a Layer.

deanmarcussen commented 3 years ago

I think what you're trying to achieve is to be able to write a widget template, without specifying in that widget template this div <div class="@String.Join(" ", Model.Classes.ToArray())">?

Unless I'm mistaken?

Wrappers are evil.

I suggest to try and avoid them as much as possible, but that's an opinionated, reasonably hard won voice of experience. We probably spend more time removing wrappers than adding them.

But it's totally possible to do already.

Just add an IShapeTableProvider which adds whatever wrapper you want.

Something like

            builder.Describe("Content")
                .OnDisplaying(displaying => 
                {
                    dynamic shape = displaying.Shape;
                    if (displaying.Shape.Metadata.DisplayType == "Detail" && shape.ContentItem.Content.Body != null)
                    {
                        displaying.Shape.Metadata.Alternates.Add("Content__Body");
                        displaying.Shape.Metadata.Wrappers.Add("SomeFunkyWrapperShapeNameHere");
                    }
                }); 

or just do that in a FlowPart template.

        widgetContent.Metadata.Wrappers.Add("ShapeName");
        @await DisplayAsync(widgetContent)

or via placement...

The voice of experience bit says, that when you do that, you'll eventually find one widget.template that needs to modify it's classes slightly. So then you'll end up with switches all through the FlowPart template, or IShapeTableProvider, like

if (widget.ContentType == "thisspecialone")
{
   widgetContent.Classes.Add("offset-3")
}
else if(...) 
{
}
sebastienros commented 3 years ago

Recommendation is to refactor FlowPart.cshtml and extract the section that defines the classes into a new intermediate shape, like FlowMetadata.cshtml based on the c# type FlowMetadataViewModel { FlowMetadata, IShape Widget } that will render:

        if (flowMetadata != null)
        {
            widgetContent.Classes.Add("widget");
            widgetContent.Classes.Add("widget-" + widget.ContentItem.ContentType.HtmlClassify());
            widgetContent.Classes.Add("widget-align-" + flowMetadata.Alignment.ToString().ToLowerInvariant());
            widgetContent.Classes.Add("widget-size-" + flowMetadata.Size);
        }

        @await DisplayAsync(widgetContent)

This won't break any existing site. Themes can now define a common way to render the chrome/positioning for widgets.

But each widget should still understand how its positioning is defined (classes right now).

Documentation of this new template should be provided in Liquid and Razor.