Orchard shapeshifting

(c) Bertrand Le Roy 2010I've shown in a previous post how to make it easier to change the layout template for specific contents or areas. But what if you want to change another shape template for specific pages, for example the main Content shape on the home page?

Here's how.

When we changed the layout, we had the problem that layout is created very early, so early that in fact it can't know what content is going to be rendered. For that reason, we had to rely on a filter and on the routing information to determine what layout template alternates to add.

This time around, we are dealing with a content shape, a shape that is directly related to a content item. That makes things a little easier as we have access to a lot more information.

What I'm going to do here is handle an event that is triggered every time a shape named "Content" is about to be displayed:

public class ContentShapeProvider : IShapeTableProvider {
    public void Discover(ShapeTableBuilder builder) {
        builder.Describe("Content")
            .OnDisplaying(displaying => {
                // do stuff to the shape
            });
    }
}

This handler is implemented in a shape table provider which is where you do all shape related site-wide operations.

The first thing we want to do in this event handler is check that we are on the front-end, displaying the "Detail" version, and not the "Summary" or the admin editor:

if (displaying.ShapeMetadata.DisplayType == "Detail") {

Now I want to provide the ability for the theme developer to provide an alternative template named "Content-HomePage.cshtml" for the home page.

In order to determine if we are indeed on the home page I can look at the current site's home page property, which for the default home page provider contains the home page item's id at the end after a semicolon. Compare that with the content item id for the shape we are looking at and you can know if that's the homepage content item. Please note that if that content is also displayed on another page than the home page it will also get the alternate: we are altering at the shape level and not at the URL/routing level like we did with the layout.

ContentItem contentItem = displaying.Shape.ContentItem;
if (_workContextAccessor.GetContext().CurrentSite
.HomePage.EndsWith(';' + contentItem.Id.ToString())) {

_workContextAccessor is an injected instance of IWorkContextAccessor from which we can get the current site and its home page.

Finally, once we've determined that we are in the specific conditions that we want to alter, we can add the alternate:

displaying.ShapeMetadata.Alternates.Add("Content__HomePage");

And that's it really. Here's the full code for the shape provider that I added to a custom theme (but it could really live in any module or theme):

using Orchard;
using Orchard.ContentManagement;
using Orchard.DisplayManagement.Descriptors;

namespace CustomLayoutMachine.ShapeProviders {
  public class ContentShapeProvider : IShapeTableProvider {
    private readonly IWorkContextAccessor _workContextAccessor;

    public ContentShapeProvider(
IWorkContextAccessor workContextAccessor) {
_workContextAccessor = workContextAccessor; }
public void Discover(ShapeTableBuilder builder) { builder.Describe("Content") .OnDisplaying(displaying => { if (displaying.ShapeMetadata.DisplayType
== "Detail") {
ContentItem contentItem =
displaying.Shape.ContentItem; if (_workContextAccessor.GetContext()
.CurrentSite.HomePage.EndsWith( ';' + contentItem.Id.ToString())) {
displaying.ShapeMetadata.Alternates.Add(
"Content__HomePage"); } } }); } } }

The code for the custom theme, with layout and content alternates, can be downloaded from the following link:
Orchard.Themes.CustomLayoutMachine.1.0.nupkg

Note: this code is going to be used in the Contoso theme that should be available soon from the theme gallery.

4 Comments

  • Is there anyway to detect which Zone the shape is headed to? I'm trying to create an alternate based on a part and a zone. In particular, I'm shooting for, "Parts.BetterMenu-AsideLeft.cshtml"

    You mentioned possibly blogging about this topic at some point in the future in your post here - http://weblogs.asp.net/bleroy/archive/2011/06/06/getting-started-with-custom-themes-in-orchard.aspx

    I looked into using the WidgetAlternates module, but it is not picking it up.

    Zone[AsideLeft]
    >> Widget
    >> >> Parts_Contents_Publish [empty] > >> Content
    >> >> >> Parts_Contents_Publish [empty]
    >> >> >> Parts_BetterMenu <-- I need the alternate "Parts.BetterMenu-AsideLeft.cshtml"

    Within Orchard.DesignerTools.Services.WidgetAlternates it looks like we could create an alternate for each part within the ContentItem, but I'm still missing something. The only part alternates I get are Parts.Contents.Publish-[Zone].cshtml

    Here's a thread on codeplex that seems to point out my difficulty:
    http://orchard.codeplex.com/discussions/275650?ProjectName=orchard

    If you could point me to a more relevant thread I'd really appreciate it.

    Thanks

  • @morgma: techniques similar to this: http://weblogs.asp.net/bleroy/archive/2011/05/23/orchard-list-customization-first-item-template.aspx can be used to override Zone rendering and add alternates on the fly.

  • Hi,
    I'm working with the IShapeTableProvider. Now we need to extend the 3 default zones of the layout shape with a filter zone. I don't see the way to implement this behavior, is it possible? I'm able to change the wrapper of the layout shape using the ondisplaying method like described here but I suppose you cannot change the definition of the shape at this level.

  • @J: not sure exactly what you are trying to do. Can we move that discussion to the discussions forum on CodePlex with some more details on what exactly you are trying to do and how it doesn't work?

Comments have been disabled for this content.