Custom List Header

Topics: Customizing Orchard
Apr 7, 2011 at 10:40 PM

What is the best way to add a custom list header that is inserted after the opening ul tag of the list but before the li tags with the items in the list? It seems like this should be easy, but I can't find a way to do it without editing CoreShapes.cs and changing how List renders and add a new variable for Header that gets added after the opening ul tag.

Coordinator
Apr 7, 2011 at 10:47 PM

The default rendering for lists is in core shapes, but that doesn't mean it can't be overridden. See http://weblogs.asp.net/bleroy/archive/2011/03/27/taking-over-list-rendering-in-orchard.aspx for example.

Apr 7, 2011 at 10:50 PM
Edited Apr 7, 2011 at 10:50 PM

There's no "list header" tag in HTML so it'd be semantically dodgy to place anything other than list items in a list :)

What you can do is add a wrapper around the List shape, wrap it in a div and just place the header inside that, before the list.

Editor
Apr 21, 2011 at 5:37 PM

Hey randompete,

I am looking to do something similar here. Basically the list shape does not render the Page Title in the content zone like a normal page shape does. What file do I need to add to my Theme to do this? I would like to just print out a container div for the list and the page title.

Apr 21, 2011 at 5:57 PM

That's a bit more tricky due to the slightly odd way list rendering currently works.

The following discussion details a workaround: http://orchard.codeplex.com/discussions/250529

Apr 21, 2011 at 10:28 PM
randompete wrote:

There's no "list header" tag in HTML so it'd be semantically dodgy to place anything other than list items in a list :)

What you can do is add a wrapper around the List shape, wrap it in a div and just place the header inside that, before the list.

When I first read this it didn't click. But now that I'm reading it again I think I see what your saying. So I know I can create a new shape like @Display(New.ListWrapper()), but how would I wrap the List shape? Would I modify the handler for the List to create a new shape when List is created, oncreated event I believe? It seems like the best place to add this wrapper shape is in CoreShapes.cs, but is there another way? Is there a simple way to "wrap" any shape in another shape? Keep in mind the shape I'm wrapping is being created in ItemController.cs in Core/Containers. Actually now I have my own ItemController.cs in my own module, so I have full control over the shapes created and parameters passed to them. Is there a tutorial on how to create sub shapes or wrap shapes in other shapes? It seems like I already learned this concept and I just forgot how to do it.

Apr 21, 2011 at 10:54 PM

There are several ways you can wrap an existing shape; you can use IShapeTableProvider, IShapeFactoryEvents, ContentHandler (for parts).

CoreShapes is just an IShapeTableProvider and you can just implement your own to add your own customizations to any existing shapes or even create your own. There isn't a Handler for a list because it's not a part. IShapeFactoryEvents and IShapeTableProvider boil down to pretty much the same thing, it's just that IShapeFactoryEvents will be run for every single shape that gets created; so you don't really want to do that unless you've got a really good reason - for instance, the Shape Tracing module uses it to build the shape data for everything (which is how I found out how to do it). So it doesn't matter where the shape is being created, it will always get passed through the shape building pipeline. I haven't really seen any documentation about this, I literally worked most of it out by studying Orchard's code (and earning a headache in the process! It gets pretty complex in there...)

So the exact way you'd do it in IShapeTableProvider is:

 builder.Describe("List")
                .OnDisplaying(displaying=>
                {
                     // Here's the tricky bit. I don't know where you want to get your title from.
                     // The list will only contain child items. But it might be possible to access the parent of the first item. Depends what you need.
                     displaying.Shape.Title = "Here's a title";
                     displaying.ShapeMetadata.Wrappers.Add("ListWrapper");
                });

Then you just need a ListWrapper.cshtml like this:

<div>
  <h2>@Model.Title</h2>
  @Display(Model.Metadata.ChildContent)
</div>

This is basically how shape tracing wraps its shape data divs around stuff. 

However since what you're trying to do is in Containers, and especially as you've got your own ItemController, the thread I've linked to above shows you how to pass the ContentItem down into the display shape anyway. So you possibly don't need the wrapper, but it's useful to know anyway.

Creating a sub shape is easy as long as you've got access to a shape factory (which is usually a dynamic object; in a Driver you get one passed in as a parameter, but in a lot of the shape event contexts it's a property called New). For instance in the OnCreated event you could call created.New.SomeShape(Foo:"Bar") - and that will render a SomeShape.cshtml will a model property of Foo (but of course other shape events can also modify that, add alternate template names, add wrappers, anything, before it gets to rendering that template)