Orchard - Add an additional shape name (i.e. an alternate) for the main List shape

StackOverflow https://stackoverflow.com/questions/23378954

  •  12-07-2023
  •  | 
  •  

Frage

Introduce the Problem

I would like to profoundly modify the layout of the Orchard CMS Tags list.

Here is an example page with Shape Tracing enabled.

The only alternate that it suggests for the List shape is ~/Themes/TheThemeMachine/Views/List.cshtml, because the page is rendering the default List shape. I would like to have other alternates that are specific to the page.

After reading Orchard list customization, I have been able to implement the default List.cshtml in razor. What I would like to do, though, is to add another alternate, such as ~/Themes/TheThemeMachine/Views/Parts.Tags.List.cshtml instead of implementing the default List.cshtml template.

The problem seems to be that the page is rendering the generic List shape.

In contrast, the blog post list page is rendering a Parts_Blogs_BlogPost_List shape, which means that a ~/Themes/TheThemeMachine/Views/Parts.Blogs.BlogPost.List.cshtml is available.

Search and Research

All quotes below are from the Orchard list customization blog post, which explains how to add a list item alternate (whereas I would like to add a list alternate).

What we really want is an alternate template... aptly called Shape Alternates... [so] enable Shape Tracing... and select a post in the list... [you will see that] we already have some possible alternates.

My example page also has some possible alternates for the List Content. Cool.

we need to somehow get into list rendering... [t]he default is defined in code... [which] can be override by a new [cshtml] template in our theme.

Okay. That makes sense. We can override the list rendering.

As Shape Tracing can show, we can override the list rendering for a blog by creating a Parts.Blog.BlogPost.List.cshtml template.

This works for alog but not for the blog Tag page (example page). You see, the blog displays a **Parts_Blogs_BlogPost_List shape and suggests an appropriate alternate but the blog tags page displays the default List shape with no alternates other than List.cshtml.

Blog Page with alternates galore enter image description here

Blog Tags Page with one alternate List.cshtml enter image description here

So, I created a List.cshtml not a Parts.Blog.BlogPost.List.cshtml template, and save it in my theme's Views directory. (One problem here is that, once we get it working, we will b overriding the default List rendering.)

enter image description here

Then I add the Razor code (copy and pasted from Bertrand's post) to override the default rendering for Lists. When I refresh the site, the browser renders a blank page. It isn't working. Here's the code:

This Does NOT Work in List.cshtml

@using Orchard.DisplayManagement.Shapes;
@{
    var list = Model.ContentItems;
    var items = list.Items;
    var count = items.Count;
    var listTag = Tag(list, "ul");
    listTag.AddCssClass("content-items");
    listTag.AddCssClass("blog-posts");
    var index = 0;
}
@listTag.StartElement
    @foreach (var item in items) {
        var itemTag = Tag(item, "li");
        if (index == 0) {
            itemTag.AddCssClass("first");
        }
        else if (index == count - 1) {
            itemTag.AddCssClass("last");
        }
        @itemTag.StartElement
        @Display(item)
        @itemTag.EndElement
        ++index;
    }
@listTag.EndElement

As a trouble shooting step, I replace the List.cshtml with <p>Hello world.</p>. Orchard renders the markup as expected. So, something is incompatible between the Razor code from Bertrand's blog and the Tags List.

enter image description here

To find out what exactly is incompatible, I try Betrand's code one line at time to see where it breaks (yup, VS would be better than WM here). At each change, I restart WebMatrix and view the results. This is the minimal code that breaks it.

The Culprit

@using Orchard.DisplayManagement.Shapes;
@{
    var list = Model.ContentItems;
    var items = list.Items;
}

list.Items isn't appropriate here. So I comment it out again and run the <p>Hello World</p> version again. Also, Shape Tracing reveals that on my Tags/tagname page, the Content Zone is now rendering the List twice. Is that normal?

enter image description here

As another step, I replace Model.ContentItems just with Model. It works. It seems that, to override the List.cshtml template, we cannot use the ContentItems property of Model. Here is the new, working code:

This Does Work in List.cshtml

@using Orchard.DisplayManagement.Shapes;
@{    
    //var list = Model.ContentItems; 
    //var items = list.Items;   

    var items = Model.Items; 
    var count = items.Count;

    //var listTag = Tag(list, "ul"); 

    var listTag = Tag(Model, "ul");    
    listTag.AddCssClass("content-items");
    listTag.AddCssClass("blog-posts");
    var index = 0;
}
@listTag.StartElement
    @foreach (var item in items) {
        var itemTag = Tag(item, "li");
        if (index == 0) {
            itemTag.AddCssClass("first");
        }
        else if (index == count - 1) {
            itemTag.AddCssClass("last");
        }
        @itemTag.StartElement
        @Display(item)
        @itemTag.EndElement
        ++index;
    }
@listTag.EndElement

Onward through the article.

So far so good, we have effectively taken over the rendering of the list, but the actual HTML [will] be... identical to what we had before [except for] the implementation.

Okay. I'm following. We want to modify the rendering not just re-implement it.

Alternates are a collection of strings that describe additional shape names for the current shape... in the Metadata.Alternates property of any shape.

Gotcha. Now, why doesn't the Tags/tagname page show an alternate other than just List.cshtml for the rendering of the List shape?

All we need to do is add to this list [of alternates]... [and make sure] to respect the lifecycle...

Great. Maybe we can we add another alternate for the List shape on the Tags/tagname page. But, doing that is different from what Betrand is explaining. While Betrand's blog post is excellent, it is explaining how to add an alternate for an item, whereas I would like to add an alternate for the list.

The List.cshtml template is where I would add an alternate for a List Item as follows:

ShapeMetadata metadata = item.Metadata;
string alternate = metadata.Type + "_" +
        metadata.DisplayType + "__" +
        item.ContentItem.ContentType +
        "_First";
metadata.OnDisplaying(ctx => {
    metadata.Alternates.Add(alternate);
});

So that...

[t]he list of alternates from Shape Tracing now contains a new item.

Where and how, though, would I add an alternate for the List shape? Bertrand has recommended to check out the Shape Table Providers blog post for this. The quotes below are from that post.

But what if you want to change another shape template for specific pages, for example the main Content shape on the home page?

This looks like a fit, because my example is the main List shape on the tags page. To do this we...

... handle an event that is triggered every time a shape named "Content" [in our case "List"] is about to be displayed. [It] is implemented in a shape table provider which is where you do all shape related site-wide operations.

Great! Here is my implementation for adding another template for the main List shape.

TheThemeMachine > ListShapeProvider.cs

namespace Themes.TheThemeMachine
{
    using Orchard.DisplayManagement.Descriptors;
    public class ListShapeProvider : IShapeTableProvider
    {
        public void Discover(ShapeTableBuilder builder)
        {
            System.Diagnostics.Debugger.Break(); // break not hit
            builder.Describe("List").OnDisplaying(displaying => {
                // do stuff to the shape
                displaying.ShapeMetadata.Alternates.Add("Tags__List");
            });
        }
    }
}

The above builds and runs but does not hit the breakpoint nor add an alternate for the List shape on the /tags page. So I looked into the Orchard.Azure.MediaServices module and its CloudVideoPlayerShape which implements IShapeTableProvider. Its breakpoint does get hit. How is my code for ListShapeProvider fundamentally different than the code for the CloudVideoPlayerShape?

Also, I installed the Orchard.Themes.CustomLayoutMachine.1.0.nupkg as suggested in Bertrand's blog post. It unfortunately no longer contains an implementation of IShapeTableProvider.

I have also looked at this szmyd post, which does not explain where to put the IShapeTableProvider code.

Further, I installed the Contoso theme from the Orchard Gallery. It works and builds after adding a reference to Microsoft.CSharp. It also includes an implementation of the IShapeTableProvider. Hooray! Comparing its ContentShapeProvider with my ListShapeProvider reveals a subtle but important difference:

Contoso.csproj

<ItemGroup>
    <Compile Include="Code\ContentShapeProvider.cs" />
</ItemGroup>

My implementation didn't include the .cs file in the compilation, because my theme has neither a .csproj nor a App_Code folder. So, I recreated my theme with the following code generation:

orchard.exe
feature enable Orchard.CodeGeneration
codegen theme My.FirstTheme /CreateProject:true
theme enable My.FirstTheme
feature enable Orchard.DesignerTools

When adding the ListShapeProvider.cs file, Visual Studio automatically added a ItemGroup/Compile entry for the file, which included the code in compilation. Hooray!

War es hilfreich?

Lösung

These two posts will help.

Here are steps of my own minimum solution.

Download and unzip Orchard.Source.1.8.zip.

Open "\Downloads\Orchard.Source.1.8\src\Orchard.sln" in Visual Studio.

Build the solution to create orchard.exe.

Generate a new theme with orchard.exe. Use CreateProject:true because you will need a csproj to include your .cs file.

orchard.exe
setup /SiteName:SITE /AdminUsername:ME /AdminPassword:PWD /DatabaseProvider:SqlCe
feature enable Orchard.CodeGeneration
codegen theme My.FirstTheme /CreateProject:true
theme enable My.FirstTheme

In VS, add a ListShapeProvier.cs file to the root (or any folder) in your theme.

ListShapeProvier

Add the following code to ListShapeProvider.cs.

namespace My.FirstTheme
{
    using Orchard.DisplayManagement.Descriptors;
    public class ListShapeProvider : IShapeTableProvider
    {
        public void Discover(ShapeTableBuilder builder)
        {
            System.Diagnostics.Debugger.Break();
            // implementation here
        }
    }
}

Build the solution.

Run Orchard.Web.

Visual Studio will break at System.Diagnostics.Debugger.Break(). If it doesn't, go to the Orchard Dashboard and make My.FirstTheme the Current Theme.

Now read Shape Shifting to implement public void Discover(ShapeTableBuilder builder).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top