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.42k stars 2.39k forks source link

Pre-populated widgets in flows/bags #6037

Open DannyT opened 4 years ago

DannyT commented 4 years ago

Suppose I have a content type with a flow part that I know 90% of the time, I want a specific widget to be included and I want that widget to be pre-populated with certain content.

For example, I have a "Case Study" content type and an "Other case studies you might like" widget that I add at the bottom of almost every case study. Normally, I just want to surface my two best case studies, but sometimes, I might want to change it or just not show that widget at all. Rather than having to remember to add that widget and then select the two same content items each time, it would be good to be able to set this up as a default/template which is pre-populated every time you create a new item.

Would that be possible and is there a suggested approach for achieving such?

ns8482e commented 4 years ago

Use Layers! and you can add other widgets based on zones using layer rules

peterkeating commented 4 years ago

@ns8482e Valid suggestion, however this would result in a lot of layers and a convoluted workflow for the author.

In the example provided, when creating a new case study, the content within pre-populated widgets will likely be changed by the author, in which case they would have to save the content item, go to layers, add a new layer, add widgets that they would need to know about to that layer, as well as putting them in the right zones. The idea of having a bag/flow pre-populated is it gives authors a sensible starting point, think of it as a recommendation of how to populate content for a bag/flow within a content item.

deanmarcussen commented 4 years ago

You should be able to do this with a handler,

possibly a ContentDisplayHandler when the context isNew, or a ContentHandler

Base it to noop when it is not the content type you want to mutate.

You may also be able to do this with workflows, and events, to have a more ui driven approach.

DannyT commented 4 years ago

A workflow event is a really nice idea as that would allow the defaults to be manageable 🤔 Thanks Dean 👌

ns8482e commented 4 years ago

when creating a new case study, the content within pre-populated widgets will likely be changed by the author,

In that case you could use widget list part, where new case study can be placed on content zone and other case studies can be placed on aside or footer zone, For other case studies, you can create widget content type with content picker field that allows content editor to select the case study

sebastienros commented 4 years ago

I'd suggest to add a workflow event when a content item is about to be edited. Might not reflect a ContentHandler existing method as it's really for Editing it the UI, but maybe in the controller itself.

The other even better suggestion, that doesn't prevent from the first, is to have a custom feature that could create "Template content items" (or a better name, like "reusable", "model") that would list predefined content items to be cloned. These content items would be displayed in the New menu item, like content types are displayed. You could have different types of predefined Article for instance, or pages based on Flow. To create them we could have an extra button like "Save as Template" in the Save button drop-down. Then this content item version would be cloned to a special storage. It might require a special view to list all the available templates, and be able to edit them. And also be careful that these items should not be Published so they don't appear on the front-end or have a public url ...

sebastienros commented 4 years ago

Another option, from a Content Type, add a custom part (PredefinedPart) that would hijack the Edit Route of this type, such that when the url is generate it redirects to a list of predefined templates for this type (if any) like when you open Excel and it shows you "Blank, Charts, Grocery list, ...). It would also add the "Save as Template" drop down for these types.

We would also need an index for the content item version that are actually templates, so we can create a view to list them.

Skrypt commented 4 years ago

Content Type templates.

sebastienros commented 4 years ago

We could have the same thing with Workflows, to not start from scratch. With wizards ...

remesq commented 4 years ago

Just throwing this out there, @DannyT, as a way I serve up static content. In my case, I have widgets that are either standalone content types I insert with Flow on a Page content type, or if I have a Named Part (Bag) that I add to a content type. In the end, everything is going to a Template.

I keep my Theme templates as .liquid/.cshtml files in my ~/Views folder, but you can easily use Admin > Design > Templates (Liquid only).

In creating the template file, I just use conditional Liquid statements to either render a field in my widget/content type, or to render stock content. For example, using an HTML field with WYSIWYG:

. . . 
{% if Model.ContentItem.Content.ContentTypeName.ContentFieldName != null and
 Model.ContentItem.Content.ContentTypeName.ContentFieldName.size > 0 }}
    {{ Model.ContentItem.Content.ContentTypeName.ContentFieldName.Html | raw }}
{% else %}
    <p>Some text explaining how to edit, static text as a fallback, 
or insert other logic to accomplish what you need.</p>
{% endif %}
. . .

One of the things I do with a Template like this is just insert it into the page and not input anything (nothing is required), and it serves up my static content where I want. You could get creative and do something like this in the else statement: {% if Request.Path == "/" %} or any other type of statements to show/hide things to accomplish what you want. I found this useful since I reused the Template, so I'm not sure if that will work for you.

Another thing I do is just insert something into my Layout file in the Theme, like this Razor code:

@{
    await DisplayAsync(await New.NameOfFile());
}

and in that file:

@if (Orchard.HttpContext.Request.Path == Href("/NameOfPath"))
{
    <zone name="NameOfZone">
        . . .
    </zone>
}

to serve up some static content on the fly, so to speak. In these instances, they are served up based on what Url is hit by the user. This is basically what the Zone feature does, but I kept the example simple - I have more complex conditions for which using Razor is just easier for me vs. the Admin or coding a Module.

Don't know if these would help you as workarounds. GL.

deanmarcussen commented 4 years ago

Thanks @remesq I think in this case we're really talking about a templating feature to allow defaults, that are then editable.

For infos, and because someone is asking me on gitter here is some sample code I use to prepopulate some bag pages I am currently stubbing

    public class PageContentCreatedHandler : ContentHandlerBase
    {
        public const string Ipsum = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. ...";

        private readonly IServiceProvider _serviceProvider;

        public PageContentCreatedHandler(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public override async Task InitializingAsync(InitializingContentContext context)
        {
            if (context.ContentItem.ContentType != "Page" || context.ContentItem.ContentType != "PageNews" || context.ContentItem.ContentType != "PageEvent")
            {
                return;
            }

            var contentManager = _serviceProvider.GetRequiredService<IContentManager>();
            var initialBlock = await contentManager.NewAsync("BlockContentFullWidth");
            initialBlock.Alter<ContentPart>("BlockContentFullWidth", a =>
            {
               a.Alter<HtmlField>("Content", f => f.Html = Ipsum); 
            });

            context.ContentItem.Alter<BagPart>("Layout", a =>
            {
                a.ContentItems.Add(initialBlock);
            });

            // context.ContentItem.DisplayText = "Add your title here";

        }
    }
}

Ideally, and this is where the templating feature would come in, I would like to be able to create these as templates, in the admin as @sebastienros has discussed above, so that my users could select from a template I have made when creating content, and then alter the content of some of the default widgets.

Ultimately my scenario will be almost identical to yours @DannyT where most of the time, there will be a related content block which I want prepoulated with 3 default items, and then the user can change them if necessary.

Until we have the templating feature, I will probably store some blocks in a custom setting (with a bag part in it), and clone the items in the bag part.