Orchard shapeshifting
- by Bertrand Le Roy
I'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
});
}
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
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.