Ways to render lists of things

If this sounds familiar, then you have probably read Bertrand's post on taking over list rendering in orchard. To me that post, together with many others, was one of the keys to getting a firmer grasp on shapes and templates, so a shout-out to all the great Orchard bloggers out there, on whose shoulders we now stand. This post will go a little deeper and show a few more techniques related to lists, shapes, placement and templates.

Let's begin.

Lists

In Orchard, when we talk about a list, we need to be a little bit more specific and determine wether we are talking about the List shape, a list of shape objects, a list of content items, or a list of anything else. Ultimately, all of these lists implement IEnumerable<T>, where T can be substituted with anything we like. We will however focus on the List shape (which implements IEnumerable<dynamic>), and regular lists of either dynamic (content item shapes where the shape type is "Content") or ContentItem (or IContent, or a specific content part type) content.

Demo Theme: Rancho Farm

To demonstrate the various methods of list rendering, we will be working on a demo theme called Rancho Farm. Rancho Farm is a fictitious farm where all kinds of animals roam. Every animal has a name, a gender, and we categorize them using the Species classification. This will give us a structure and some data to work with.

Since I like to cover as much detail as I can, we will start from scratch and generate a theme. If you have done that enough times before, you should feel free to skip the setup section. I will not however describe the steps to setup a new Orchard installation - that has been described many times before.

Generating the Theme

In order to be able to generate a theme, we need to enable the Code Generation feature. We can do this via the dashboard (admin menu -> Modules), or via the Orchard command prompt. Let's go with the latter (make sure you have setup a fresh new Orchard installation). 

When generating our theme, we want it to be based on TheThemeMachine, which ships out of the box with Orchard.

CodeGen Theme RanchoFarm

To launch the Orchard command prompt, open Windows Explorer, navigate to the bin folder of your Orchard installation, and launch Orchard.exe. On my machine, the path to that file looks like this:

When Orchard.exe has started and initialized the Orchard shell, enter the following command to enable the code generation feature and hit enter:

feature enable Orchard.CodeGeneration

The result should look like this:

Now that the code generation feature is enabled, it is time to generate our theme. Remember, we want to base the theme on TheThemeMachine so we get a default look and feel as well as a default Layout.cshtml file that we inherit.

With the Orchard command prompt still open, enter the following command and hit enter:

codegen theme RanchoFarm /BasedOn:TheThemeMachine /CreateProject:true /IncludeInSolution:true

The output should look like:

Because we specified the /CreateProject:true switch, Orchard will have generated a seperate project file for our theme. If we didn't specify that switch, our theme would have been included as part of the Theme.csproj file. The advantage of the latter is that we have one less assembly that needs to be compiled, but the advantage of the former is that it is now easier to upgrade Orchard to newer versions, especially when we add references to our project. In the end, both options are fine, so feel free to choose one or the other.

If you haven't done so already, open the Orchard.sln file in Visual Studio and observe our updated project structure (if you did have the solution open, Visual Studio will prompt you to reload the solution - click "Reload All"):

Now that we have our new theme in available, let's launch the Orchard website and set our theme to be the current:

As you might have noticed, our generated theme contains no views nor CSS files. And yet, if you now navigate to the front end, you will see a theme exactly like TheThemeMachine:

The reason of course is the fact that we configured our theme to be based on TheThemeMachine when we had Orchard generate it. The theme manifest shows this fact:

You can change these settings at any time.

Allright. Moving on!

Sample data

Next, let's set up some content that we can work with when rendering lists. We will do the following:

  1. Define the Species taxonomy and setup some terms.
  2. Define the Animal content type and add a BooleanField representing the animal's gender and a TaxonomyField representing the animal's species. We will also attach the AutoroutePart and TitlePart. The TitlePart will represent the name of the animal, and AutoroutePart will provide a URL to our animal.
  3. Create some animals.
  4. Create a Query and a Projection to list all of our animals

Once this is setup, we will start looking at customizing the rendering of these animals in a variety of ways.

Defining the Species taxonomy

To define the Species taxonomy, go to Taxonomies and create a new taxonomy called Species:

Next, import the following list of terms (feel free to add more if you want):

Cat
Chicken
Cow
Dog
Fish
Frog
Rabbit
Sheep

Notice that I said "import" instead of "manually create each of the following term". I don't know when this feature was introduced, but we can import a list of terms all at once by clicking the "Import" link next to our Species taxonomy:

Specify one term per line line and hit Import:

That went fast:

A very helpful feature when you need to create taxonomy terms.

Defining the Animal content type

Go to Content Definition -> Create New Type to create a new Animal type:

Hit Create:

Select the Autoroute part and Title part and hit Save:

In addition to the selected parts, we will add two fields: a Boolean field representing the animal's gender and a Taxonomy field representing the animal's species.

Hit Add Field:

Enter the value "Gender" for the display name as well as the technical name and select the Boolean Field as the field type:

Add another field, this time specifing Species as the display name and technical name, and selecting Taxonomy Field as the field type:

Next, let's configure our Gender field to read "Male" when the selected value is true and "Female" when the selected value is false. Also change the Selection Mode to Radio buttons:

Configure the Species field to use the Species taxonomy. Also configure this field to be required and allow for multiple values, and optionally to display as an autocomplete field (not required, but kind of I like that look better when editing, so I don't have to keep expanding the UI):

And hit Save:

You might be wondering why we allow an animal to be classified as multiple species, because it seems weird for an animal to be a frog and a rabbit at the same time for example, right? But I have been to Rancho, and I am telling you, I have seen an animal whose species is unclear, so we need to ability to specify multiple values. More importantly, we should allow ourselves to have a little fun.

Creating sample animals

Now that our Animal content type is in place, let's create some sample Animal content items. I have prepared an animals.recipe.xml recipe file that you can download and import if you like. This file includes both the Species taxonomy as well as its terms, and some Animal content items. Remember to enable the Import Export feature if you want to import the file. I am lazy, so I imported the file. I now have 15 animals roaming Rancho Farm:

Now that we have some sample data, it is time to create a Query and a Projection of that query, so we have something to work with when customizing the list being rendered.

Query and Projection

Orchard comes with a powerful module called Projections. This module enables a dashboard user to visually design a Query. When a query executes, it yields a list of content items based on its configured Filters. Thes content items are ultimately rendered through a Projection

So the query is responsible for fetching the data, and a projection is responsible for actually displaying that data. The data being returned from a query are always content items.

Let's setup a query that returns all Animals.

Animals Query

Go to Query and create a new query named Animals:

Now that the query is created, we need to add a filter so that only content items of type Animal are returned:

Select the Content Types filter:

Select the Animal content type and hit Save:

Now that we have a query in place, let's create a Projection to render our animals:

After hitting Save, navigate to the front end of the site (for example by clicking the site's title in the top left corner) and click on the "Our Animals" menu item to see our animals:

Various Ways To Customize Lists

The default look of the list being rendered isn't bad, but generally we will want to change the markup, so let's see what we can do. There are at least 2 ways we can customize the above list:

  1. Overriding the Content shape for the Animal content type for the Summary display type. This enables us to customize each item, but does not enable us to change the list rendering itself;
  2. Specifying a different Query Layout on our Animals query, for example the Shape layout, and completely take over the rendering of the list. This enables us to define exactly how the entire list is rendered.

For each of the above options, there are also at least two ways to render an individual item:

  1. Let Orchard render the item using the content manager's BuildDisplay method. For each rendered content item, we can control where its part and field shapes go via Placement.info. This is the most resilient option, as it enables users to change the content type structure as they see fit. The disadvantage is less control over where exactly you render things, as we shall see.
  2. Completely take over the rendering ourselves, bypassing Placement.info alltogether. This is the least resilient option, as the view might easily break when the user changes the content type structure (unless you code defensively against that by checking for parts and fields being there or not). The advantage is full control over every aspect of the generated HTM, as we will also seeL.

Let's start with combination 1/1: we will override the Content shape template for the Animal content type, and use Placement.info to control where we render the part and field shapes.

Before we start copying a file, let's take a step back and talk about what we are going to do first. As we learned earlier, a Projection renders a list of content items. More specifically, the ProjectionPartDriver looks at the selected layout of the ProjectionPart (which is attached to the Projection content type), and delegates rendering of the content items returned from the query to that layout. If no layout was specified (as in our case), it will simply project the content items to a list of content item shapes and add them to a List shape:

Orchard.Projections/Drivers/ProjectionPartDriver.cs:

The above code creates a new shape of type List, and then projects the content items into a list of content items shapes. Notice that each content item is rendered using the "Summary" display type. As we learned in the previous post, the BuildDisplay method of IContentManager returns a shape of type "Content". This is important information, because when we override the Content shape view (a.k.a. template), we only want to do so for the Animal content type and for the Summary display type.

Let's first copy over the default Content.Summary.cshtml shape template from the Contents module (which lives in the Orchard.Core project):

Paste the copied file into the root of our theme's Views folder:

As just mentioned, we only want to customze the Content shape template for our Animal content type, so we need to rename our file into something else. The question is, how do we determine which filename that is supposed to be? We could check the documentation on Accessing and rendering shapes and be done with it. That would be totally OK, and is probably what you will do most of the time. But let's try another way of discovering available alternates for shapes. I think you will find this technique invaluable when theming Orchard websites.

Discovering alternates

Let's imagine there are no docs and there is no such thing as a Shape Tracer. How would we find out what alternates are available to us for a given shape? As it turns out, it is quite simple: we set a breakpoint in the shape template for which we want to find alternates, and inspect the Model's Metadata's Alternates property (Model being the Shape itself, as we learned in the previous post).

Go ahead and set a breakpoint on line 3 of Content.Summary.cshtml in our theme project. Make sure you have saved all documents (or built the solution), and refreshed the website to make sure it is warmed up before attaching the debugger. This is important, because if we attach the debugger to IIS / IIS Express before that, we will have to wait much longer for Orchard to warm up (since the debugger slows execution), and that's just a waste of time.

Setting the breakpoint:

Attaching the debugger:

Now go back to the "Our Animals" page and hit CTRL+F5 (to execute a hard-refresh to break through the output cache), and watch your breakpoint being hit:

When you hover your mouse cursor over the Model property, we can see the current properties added to the shape, including the Metadata property, which in turn shows the Alternates collection property.

As you see there are 7 alternates available at the point where the Content shape is being rendered. We are interested in the 4th alternate: "Content_Summary__Animal". So, that means we should rename our "Content.Summary.cshtml" template to "Content.Summary-Animal.cshtml", right?

Well I don't blame you for thinking that, since we established earlier that when mapping a shape name to a template file name, a single underscore gets replaced with a period, and a double underscore gets replaced with a hyphen. This totally tripped me up when I first compared the list of available alternates against the actual file name of my template for the Summary display type. Apparently there is another rule in place.

As it turns out, the Display Type portion of a shape alternate gets special treatment when mapping it to a template file name. This is due to how the BasicShapeTemplateHarvester works, which is as follows:

  1. When Orchard is first started, it finds all of the Razor view files for every module and theme. Based on these filenames, a so called shape table is constructed.
  2. During the parsing of the filename (ignorning the file name extension), the convention is that the display type of the shape is the last portion of the filename after a dot. For example, of you have a file name called "Content-Animal.Summary", "Summary" will be recorded as the display type. Hyphens in a filename will be translated into a double underscore, and used as a so called breaking separator, which is used to further split the file name string into two. The harvester now has 3 components to build a shape name: The base name ("Content"), the display type ("Summary"), and the portion that comes after the base name ("Animal"). Let's refer to the latter as the "shape specializer".
  3. The 3 components are used to construct the shape name as follows: BaseName + "_" + DisplayType + "__" + ShapeSpecializer.

If I just lost you completely with what I just said, don't worry. Just remember that shape alternate syntax is mapped in a special way to a filename, and vice versa. So when we see an available alternate like "Content_Summary__Animal" that we want to use, we need to create a file name called "Content-Animal.Summary.cshtml". Similarly, if we wanted to use the alternate "Content_Summary__Animal__Species__species__frog" to provide a specialized version for the frog species for the Summary display type, we would have to create a filename called "Content-Animal-Species-species-frog.Summary.cshtml", essentially moving the display type portion to the end of the filename. A nice table of mappings can be found on this page: Accessing and rendering shapes in the section called "Naming Shapes and Templates".

Allright, so let's pick an alternate. Since we want to customize the Content shape template for the Summary display type and for the Animal content type, the "Content_Summary__Animal" alternate seems perfect, so let's rename our copy of "Content.Summary.cshtml" to "Content-Animal.Summary.cshtml" and customize it.

Currently, that view looks like this:

Views/Content-Animal.Summary.cshtml:

As you can see, it renders an article element and some nested elements. By default, the Contents module (from which we copied this view) defines some zones local to the Content shape: Header, Meta, Content and Footer. But we, my friend, are free to get rid of all of them, or replace them with a completely different set of zones as we see fit. As an experiment, let's do the following:

  1. We simplify the view by getting rid of everything in there.
  2. We render a single DIV element and nested within that a single zone named Animal, and place all shapes into that single zone.

Our updated view looks like this:

Views/Content-Animal.Summary.cshtml:

Yeah, nice and clean.

In order for the various shapes to appear in that zone, we will need to configure them using Placement.info. Currently, that file looks like this:

We will delete all of the green stuff and add our own <Place> elements there. But how do we know what the names of the shapes are? Let's think about that for a minute.

This is what we know: we know that our Animal content type consists of the following parts and fields:

  • CommonPart
  • AutoroutePart
  • TitlePart
  • TaxonomyField (named as "Species")
  • BooleanField (named as "Gender")

We also know that the content part and content field drivers are responsible for creating shapes that render these parts and fields, and that the Placement.info file is used to control where these shapes go onto the Content item shape, as we learned in the previous post.

With this information in hand, we can go ahead and discover all the shapes that we need to know about by simply navigating to the driver of each part and field and look at their Display method (since that's the method that will be invoked when we render a content item). We could also check out the Placement.info file of the module that hosts the driver. I usually checkout the driver first, and then copy and paste the entries from the Placement.info file that I want to customize in my theme. Anyway, let's start with the CommonPartDriver.

CommonPart

Orchard.Core/Common/Drivers/CommonPartDriver.cs:

As you can see, we have 3 shapes at our disposal that we can choose to render. As their names indicate though, each shape is intended for use with a specific display type (although not at all mandatory). In our case, let's choose to not render any of these shapes at all, so add the following <Place> element to our Placement.info file:

<Place Parts_Common_Metadata_Summary="-" />

The reason we chose the second shape instead of any of the other two is that I knew by looking at the module's Placement.info file these shapes wouldn't be rendered anyway for the "Summary" display type.

Also, because we want this shape only to not appear for the Summary display type, we should nest this element inside of a <Match DisplayType="Summary"> rule element, like this:

<Match DisplayType="Summary">
    <Place Parts_Common_Metadata_Summary="-"/>
</Match>

Next up: AutoroutePart.

AutoroutePart

As it turns out, the AutoroutePartDriver class doesn't implement a Display method, so there are no shapes for us to place. It does implement the part's editor shapes as well as import/export logic, but none of that concerns us in this article.

TitlePart

The TitlePartDriver, which lives in the Title module of the Orchard.Core project, implements the Display method as follows:

Orchard.Core/Title/Drivers/TitlePartDriver.cs:

Like the CommonPart shapes, we only care about the Parts_Title_Summary shape, since our Animal content item is rendered with the Summary display type. This time, we do want to show the shape, so add the following <Place> element to our Placement.info file:

<Match DisplayType="Summary">
    <Place Parts_Common_Metadata_Summary="-"/>
    <Place Parts_Title_Summary="Animal:0"/>
</Match>

Notice that we specified the value "Animal:0" - this means we want to add the Parts_Title_Summary shape to a zone called "Animal" at position "0".  We already changed our view file to render exactly that zone.

Next: the Species field of type TaxonomyField.

TaxonomyField - Species

The Display method of the TaxonomyFieldDriver in "Orchard.Taxonomies/Drivers" looks like this:

Orchard.Taxonomies/Drivers/TaxonomyFieldDriver.cs:

Only one shape called Fields_TaxonomyField is returned here (admittedly a bit of a redundant name, since the name already starts with Field, so no need to end the shape with Field as well). This means we can add a <Place Fields_TaxonomyField="Animal:1"> element to our Placement.info file. But before we do that, let's talk about what happens if we had another TaxonomyField attached to our type. 

If we did have another TaxonomyField attached to our type, then both fields would be placed in the exact same zone and position (one of them appearing before the other). What if we didn't want that? What if we wanted one of these fields to appear in ZoneA and another one in ZoneB? Well, that's where shape differentiators come in. As you see in above code snippet (boxed in red), we are passing in a second argument to the ContentShape method, being the "differentiator". This value is used, you guessed it, to differentiate between field shapes. The value of this differentiator is up to the driver's implementation, but it is commonly implemented by simply taking the field's name. In our case that would be "Species". If you're curious, the GetDifferentiator method of TaxonomyFieldDriver looks like this:

 1: private static string GetDifferentiator(TaxonomyField field, ContentPart part) {
 2:    return field.Name;
 3: }

It simply returns the name of the field.

We can use the value of the differentiator in our placement file like this: <Place Fields_TaxonomyField-Species="Animal:1">. Notice the hyphen and then the value of the differentiator. Go ahead and update our Placement.info:

<Match DisplayType="Summary">
    <Place Parts_Common_Metadata_Summary="-"/>
    <Place Parts_Title_Summary="Animal:0"/>
    <Place Fields_TaxonomyField-Species="Animal:1"/>
</Match>

And finally, our BooleanField.

BooleanField - Gender

The Display method of the BooleanFieldDriver looks like the following:

It also returns just one shape, called Fields_Boolean. Let's add it to to the end of the Animal zone:

<Match DisplayType="Summary">
    <Place Parts_Common_Metadata_Summary="-"/>
    <Place Parts_Title_Summary="Animal:0"/>
    <Place Fields_TaxonomyField-Species="Animal:1"/>
    <Place Fields_Boolean-Gender="Animal:2"/>
</Match>

That's it! Our complete Placement.info file should now look like this:

With these changes in place, our "Our Animals" page looks like this:

Granted, that looks pretty much the same as before we started making changes. However, if we look at the generated HTML, we can definitely see our customizations:

As you can see, all of the 3 shapes that we intended to render, are being rendered - we can tell because there aren' any <article> tags surrounding our animals. Now let's say we wanted to further customize how this is rendered by overriding the shape templates so that each animal would show as something like: "Leod is a male Frog".

The first thing to do is swap around the position of the Species field shape and the Gender field shape by updating Placement.info:

Now let's copy the shape template for the BooleanField shape into our theme and rename it so that it will only be used for our type and the "Summary" display type. We can find this file in the Orchard.Fields module in the "Views/Fields" folder, and is called "Boolean.cshtml". Copy that file into our theme's Views folder and rename it to: "Fields.Boolean-Animal-Gender.cshtml" (I attached the debugger again to find the available alternates, and chose to use "Fields_Boolean__Animal__Gender". I could have chosen "Fields_Boolean__Gender" as well, if I wanted to use the same markup for any BooleanField named "Gender", regardless of the type it is attached to).

Note: Instead of naming the file name "Fields.Boolean-Animal-Gender.cshtml", we could have also chosen to create a subfolder called "Fields" and name the file "Boolean-Animal-Gender.cshtml". The same goes for content part shape templates. If you have a lot of part and field shape templates, you might want to consider doing this to try and keep things a bit more tidy.

I went ahead and changed the contents of our copied file to this:

That takes care of the "is a male" or "is a female" portion. Let's now copy the default TaxonomyField shape template "Orchard.Taxonomies/Views/Fields/TaxonomyField.cshtml" to our theme and rename it as: "Fields.TaxonomyField-Animal-Species.cshtml". I changed its contents to:

Note: In order for intellisense to play nice, I added a project reference to Orchard.Taxonomies to the theme.

When we now refresh our front end page, it looks like this:

OK, well that is a step into the right direction. But really, is this the limit of what we can do when customizing our Animal content item shape? Of course not. I went this route to demonstrate how you can customize the Content shape template by rendering arbitrary zones and then use Placement.info to place part and field shapes (or any other ad-hoc shapes you might think of). But now that we have seen that, let's redo our "Content-Animal.Summary.cshtml" template and render exactly what we want: a single line per animal that reads: "[Animal name] is a [gender name] [species name]", for example: "Rilo is a Chicken Dog".

In order to pull this off, we need to be able to access the information that is stored in the various parts and fields of the Animal content item. There are two approaches to this: the strongly typed approach and the dynamically typed approach. Let's look at both.

Given a content item or content part (both of which implement IContent), we can use the .As<TPart>() extension method to access any part attached to the content item. Unfortunately (and as far as I know), there is currently no extension method to easily get the content field attached to a content part. But this is not hard to do when using LINQ. And even simpler when we use the magic of dynamic, because ContentItem and ContentPart are both derived from DynamicObject, and implement a syntax that enables us to access parts and fields as if they were properties on the content item. We'll demonstrate the first approach first, and then update the view to take the latter (dynamic syntax) approach.

Strongly typed access

The following shows strongly typed part and field accessor code:

Views/Content-Animal.Summary.cshtml:

 1: @using Orchard.ContentManagement
 2: @using Orchard.Core.Title.Models
 3: @using Orchard.Fields.Fields
 4: @using Orchard.Taxonomies.Fields
 5: @{
 6:     var contentItem = (ContentItem)Model.ContentItem;
 7:     var titlePart = contentItem.As<TitlePart>();
 8:     var animalName = titlePart.Title;
 9:     var speciesField = (TaxonomyField)contentItem.Parts.SelectMany(x => x.Fields).Single(x => x.Name == "Species");
 10:     var genderField = (BooleanField)contentItem.Parts.SelectMany(x => x.Fields).Single(x => x.Name == "Gender");
 11:     var genderDescription = genderField.Value == true ? T("male") : T("female");
 12:     var species = String.Join(" ", speciesField.Terms.Select(x => Html.ItemDisplayLink(x)));
 13: }
 14: <div class="animal">
 15:     @T("{0} is a {1} {2}", animalName, genderDescription, species)
 16: </div>

(I added a project reference to Orchard.Fields so I could import the Orchard.Fields.Fields namespace without red warnings. Orchard will run fine without the reference, but I find it more comfortable to develop themes with proper project references in place).

Let's go over our updated view.

On line 6, we are accessing Model.ContentItem. The ContentItem property is set by some calling code that invokes our drivers.

One line 7, we access the TitlePart using the .As<T>() extension method, so we can in turn access its Title property, which represents our animal name as seen on line 8.

On line 9, we execute a LINQ query where we look for the field named "Species". We could optimize this query by first selecting the content part of which we know will hold this field (the part being the implicitly created "Animal" part), but I decided to keep it like this for now, because we are going to change this to the dynamic syntax soon anyway.

On line 10, we do the same as line 10, but this time for the Gender field.

On line 11 and 12 we prepare some string values for rendering, and on lines 14 through 16 we finally render the HTML to represent a single Animal for the "Summary" display type.

Now the page looks like this:

I don't know about you, but I am liking this much better. More importantly, I am liking this level of control.

The HTML looks like this:

We managed to succesfully access and render various parts and fields in a Content shape template, taking full control over it. Now let's see how the dynamic syntax mentioned earlier would look like:

Dynamically typed access

 1: @using Orchard.Taxonomies.Fields
 2: @{
 3:     var contentItem = Model.ContentItem;
 4:     var titlePart = contentItem.TitlePart;
 5:     var animalName = titlePart.Title;
 6:     var speciesField = (TaxonomyField)contentItem.Animal.Species;
 7:     var genderField = contentItem.Animal.Gender;
 8:     var genderDescription = genderField.Value == true ? T("male") : T("female");
 9:     var species = String.Join(" ", speciesField.Terms.Select(x => Html.ItemDisplayLink(x)));
 10: }
 11: <div class="animal">
 12:     @T("{0} is a {1} {2}", animalName, genderDescription, species)
 13: </div>

As you can see, that syntax is much nicer. Unfortunately we loose intellisense when dynamics, but here is a little cheat sheet for the most comonly used shape properties, parts and fields, called the Orchard Cheat Sheet.

On line 3, we still assign Model.ContentItem to a local variable, but without casting it to its actual ContentItem type. Because Model is typed as dynamic, Model.ContentItem will by typed as dynamic too. Because of that, we can now leverage the dynamic behavior implemented by the ContentItem type, which is why we can now access its parts as if they were properties.

When we attached a field to our Animal type through the UI (or imported the animals.recipe.xml file), Orchard created a so called implicit content part that has the same name as the content type: "Animal". The reason for this is that you can only attach content fields to content parts, not types (despite the fact that the dashboard likes to fool you into thinking that). For this reason, we can now access fields attached to the Animal part like this: contentItem.Animal.Species, as you can see on line 6.

The reason we cast that Species field to its strongly typed TaxonomyField type is because we can't use dynamically typed variables in LINQ expressions.

Allright, this was helpful. But dude, we were promised to have full control over our HTML. You would think that includes the way the list is rendered, right? It can't be that we can only adjust the individual list items? What if for whatever reason we wanted to render a TABLE and TR elements, instead of UL and LI elements? Or maybe nothing at all, just the individual Animal shapes? How on earth would we do that?

As it turns out, the List (created by the ProjectionPartDriver), too, is just a shape. Let's see what alternates are available there. Go ahead, set a breakpoint somewhere in our "Content-Animal.Summary.cshtml" file, attach the debugger, and let's check out the stack trace to find the List shape so we can inspect its available alternates:

Ugh. That was not straightforward to find - initially I thought I needed one call above the boxed call in the Call Stack window, since that is where the List shape method is implemented. However, that method didn't accept the Shape argument, so there was no way for us to inspect its alternates. However, one call up the stack provides us with a reference to the List shape instance via the displayContext argument, which works just as well.

However, those 2 available alternates are pretty useless unless we're OK with the fact that EVERY OTHER list in our theme would render animals - which we obviously should not be OK with. Not only would it not work (since a List shape could hold any other type of shape), but more importantly, we were promised full control!

So how do we do that? Write a shape table provider and add alternates based on some condition? We could certainly do that, and I would have you do it of there were no other choice. Luckily, we do have a choice. The amazing Orchard.Projections module lets use decide what Query Layout we want to use when rendering a list of content items, and this will be the key to our solution.

Let's see what options are available. Navigate to Queries, select our Animals queries, and add a new Layout:

We'll see the following available layouts:

So far we have seen the Html List layout in action (or rather, a default implementation that does the same as that layout).

The "Raw" layout lets us define various aspects of the list to be rendered. For example, we can specify the list element (UL by default) and list item element (LI by default), amongst other aspects.

The "Grid" layout will render a grid, where you can configure the tag to use for the grid itself, the rows and the cells.

The "Shape" is the simplest yet most flexible version of all, as it is entirely up to the theme developer to provide a shape name to be used for rendering the list of content items, providing the maximum level of freedom. This is the Layout we will add to our query:

Add Shape layout:


We could change the DisplayType to use for the content items to be rendered if we wanted to, but Summary is just fine for our purposes. You could change this to anything you like if you have multiple lists rendering the same type of content, but where you wanted to use a different views.

Important is the Shape type: here we specify the name of the shape that will be responsible for rendering the list of content items. Since we specified "AnimalsList" as the shape type, we need to create a Razor view file in our Views folder called "AnimalsList.cshtml". I created that file with the following contents:

 1: @using Orchard.ContentManagement
 2: @using Orchard.Core.Title.Models
 3: @using Orchard.Fields.Fields
 4: @using Orchard.Taxonomies.Fields
 5: @{
 6:     Style.Include("table.css");
 7:     var animals = ((IEnumerable<ContentItem>) Model.ContentItems).ToList();
 8: }
 9: <p>
 10:     @T.Plural("There are no animals roaming our farm.", "There is 1 animal roaming our farm.", "There are {0} animals roaming our farm.", animals.Count)
 11: </p>
 12: <table class="items">
 13:     @foreach (var animal in animals) {
 14:         var titlePart = animal.As<TitlePart>();
 15:         var animalName = titlePart.Title;
 16:         var speciesField = (TaxonomyField)animal.Parts.SelectMany(x => x.Fields).Single(x => x.Name == "Species");
 17:         var genderField = (BooleanField)animal.Parts.SelectMany(x => x.Fields).Single(x => x.Name == "Gender");
 18:         var genderDescription = genderField.Value == true ? T("male") : T("female");
 19:         var species = String.Join(" ", speciesField.Terms.Select(x => Html.ItemDisplayLink(x)));
 20:  
 21:         <tr>
 22:             <td>@T("{0} is a {1} {2}", animalName, genderDescription, species)</td>
 23:         </tr>
 24:     }
 25: </table>

(Notice I included a stylesheet "table.css" - it will be part of the theme download at the end of this article).

Just for fun I decided to render a table of our animals.

The key line about this AnimalsList.cshtml view is line 7, where we get a reference to the content items that were yielded by the query. The rest of the code merely iterates over this list and access the various parts, fields and their properties to render them out.

To test how this looks, make sure to update the "Our Animals" Projection to use the query and the new Projection Layout we created:

And this is how our page looks like on the front end:

Conclusion

The Orchard shape mechanism is rich, flexible and powerful, but can be daunting for the beginning Orchard developer. However, understanding where shapes come from, what alternates are availble, how to discover them using the debugger, and how they translate to Razor files should provide some extra handles to help take over rendering in Orchard like a wizard.

Happy rendering!

Download the sample RanchoFarm theme.

Download the animals.recipe.xml.

20 comments

  • ... Posted by Matt Burnside Posted 11/07/2014 07:26 AM

    Lovely stuff. Crystal-clear and it WORKS. Looking forward to any and all future posts from you.

  • ... Posted by JSR Posted 11/07/2014 07:26 AM

    How can I list all the animals in a GRID format (i.e. table with 2 columns); Each column containing the animal name & details ? I tried the Query Drid format but it does not seem to work.

  • ... Posted by Thomas Dahlberg Posted 11/07/2014 07:27 AM

    Nice! If I would like to be able to sort and filter the list on the client-side? Is that possible with a projection?

  • ... Posted by Sipke Schoorstra Posted 11/07/2014 07:28 AM [http://www.ideliverable.com]

    Sure! But not out of the box - you would have to implement that sorting yourself. There are JavaScript libraries out there that can turn HTML tables into sortable tables, so if you render your projection as an HTML table, you're all set.

  • ... Posted by Thomas Dahlberg Posted 11/07/2014 07:28 AM

    Yeah, I'm using javascript right now. But it's a mess combined with a paginator and a couple of thousand items. The area of use is a webshop where i would like to be able to not only sort but to filter out all the green ones (for example).

  • ... Posted by Sipke Schoorstra Posted 11/07/2014 07:30 AM [http://www.ideliverable.com]

    In that case you would have to enable Pagination (supported by Projections) and intercept the pagination link actions using JavaScript, firing the requests using AJAX. The IDeliverable.Widgets module (https://github.com/IDeliverable/IDeliverable.Widgets) supports ajaxifying content types (and widgets), and will soon support pagination as well (there's currently an outstanding issue on that (https://github.com/IDeliverable/IDeliverable.Widgets/issues/6)

  • ... Posted by David Redmond Posted 11/18/2014 07:10 AM

    I owe you guys a beer! I spent a good few hours trying to work out how to display the projection list in a custom layout and finally stumbled across your blog post.

    Thanks!

  • ... Posted by Steve Curran Posted 01/23/2015 07:14 PM [http://steviethescotsman.com]

    Great article for developers like myself who are looking at how to have complete control using razor and projections. Thanks for taking the time to post Cheers Steve

  • ... Posted by Matt Dunn Posted 02/01/2015 04:44 PM [http://mattdunndc.com/]

    Wow, this article is outstanding. Thanks for posting!

  • ... Posted by Joseph Pu Posted 02/12/2015 02:50 PM

    This is awesome I've been looking for a clear explanation of this for days I think they should put this in the orchard documentations. This answers every questions I had about the mystery of not how to style a projection using shapes but how shapes work etc.. its clear and straight forward you just made a follower out of me!

  • ... Posted by Stephen Posted 02/25/2015 10:51 PM

    Thank you so much. This article really helped me understand some of the core concepts of Orchard. We have been creating complete custom widgets every time we want to display a list of things. I realise now that we have been re-inventing the wheel and should have just used Queries and Projection Widgets with a custom view. That would have been so much more simple! Many thanks.

  • ... Posted by Armando Posted 05/14/2015 11:30 PM

    Thank you so much for this article! I'm pretty new to Orchard, and the razor syntax.. In your shape layout example, using the shape to have full control of the values being rendered is exactly what I've been looking for, but I'm a little lost when trying to rewrite the code for the results I'm hoping for. I have a custom content type called 'course' and instead of a boolean and taxonomy fields, I'm using a date and time and text field. So I've written the following code inside the for each loop:

    var startField = (DateTimeField)course.Parts.SelectMany(x => x.Fields).Single(x => x.Name == "start");'
    
    var locationField = (TextField)course.Parts.SelectMany(x => x.Fields).Single(x => x.Name == "location");'
    
    // This is where I have trouble, I am not sure how to write the next couple of lines..
    var startTime = startField. ? ;
    var locationText = locationField. ? ;
    

    Any help would be greatly appreciated!!

  • ... Posted by rtpHarry Posted 08/21/2015 11:49 AM

    There is a broken link in the Creating Sample Animals section.

    The correct link is:

  • ... Posted by rtpHarry Posted 08/21/2015 10:32 PM

    Feedback

    Really good tutorial so far, getting me back up to speed after a few months away from Orchard. Couple of other little bits I noticed while following it:

    Just before the strongly typed section the example is wrong, its missing the gender so should say "Rilo is a female Chicken, Dog"

    The code snippet in the strongly typed section didn't work as expected. The species was output as a block of encoded html. To combat this I changed line 12 to this:

    IHtmlString species = new HtmlString(String.Join(", ", speciesField.Terms.Select(x => Html.ItemDisplayLink(x))));

    It worked but I'm not sure if this is the best practice putting it through T()? The same issue occurs in both the dynamic example and the AnimalList snippet.

    There are a couple of minor typos as well:

    • We could certainly do that, and I would have you do it of there were no other choice.
    • (In Conclusion) What alternates are availble

    Praise

    This article was amazing, I have such a clearer view of the whole system now. I'm going to apply the ideas in this to set up a CMS powered testimonials section and take over the rendering to put it in a Bootstrap carousel. Thanks for the inspiration.

    Questions

    Was there a reason why you just called it AnimalList.cshtml? I guess it obviously works but I was expecting you to suggest AnimalList.Summary.cshtml. Looking in the Shape Tracer it doesn't suggest it as a possible alternative either but going with the testimonials idea maybe I would implement a Tiles and a Carousel DisplayType, how would I access between them?

    Is there any chance you are planning a version of this where you show us how to set this stuff up on the code side rather than using the UI? I'm just thinking I need to remember all these steps to implement it in the next site but if I could code it then I could wrap it up in a module or include it in a base theme to provide features between sites.

    Thanks again for this article!

  • ... Posted by rtpHarry Posted 08/21/2015 10:44 PM

    @Armando

    You can figure out what to write next using the same techniques that Sipke has taught us in this article.

    For example, to know what you can use after startField. you would firstly go to the View for the DateTimeField type. We can logically guess that this is going to be with the other fields in the Orchard.Fields project but if you can't find something just press F12 on the type to go to its definition automatically.

    Looking in the Orchard.Fields project we can use the Views folder as a first stop, /Orchard.Fields/Views/DateTime.cshtml. This shows us that the time could be used in code with this line:

    var startTime = startField.Model.Editor.Time;

    To see what else is on offer you can check the DateTimeFieldDriver.cs class. In Display() you will see the DateTimeFieldViewModel being created and passed through which contains all the properties for that type.

  • ... Posted by Alberto León Posted 09/27/2015 06:22 PM

    A note, if you want to render the html with razor you can't do @species because you get escaped html You should do @Html.Raw(species) to obtain the html links as they must be rendered.

  • ... Posted by Ricelle Posted 01/15/2016 01:06 PM

    @rtpHarry I have an expiry date that is similar to @Armando's example, I have the following and it does not work:

    var exField = (DateTimeField)holidaydealsTerm.Parts.SelectMany(x => x.Fields).Single(x => x.Name == "Expirydate"); var TheExpiryDate = exField.Model.Editor.Date ;

    I either get an error, or no result.

  • ... Posted by Ricelle Posted 01/15/2016 01:55 PM

    I ended up using:

    var expiryField = (DateTimeField)holidaydealsTerm.Parts.SelectMany(x => x.Fields).Single(x => x.Name == "Expirydate"); ``var IexpiryDate = expiryField.DateTime;''

    output ``<span>@Display.DateTime(DateTimeUtc: IexpiryDate, CustomFormat: T("d MMMM yyyy"))</span>''

  • ... Posted by Ricelle Posted 01/15/2016 01:57 PM

    I ended up using:

    var expiryField = (DateTimeField)holidaydealsTerm.Parts.SelectMany(x => x.Fields).Single(x => x.Name == "Expirydate"); var IexpiryDate = expiryField.DateTime;

    output <span>@Display.DateTime(DateTimeUtc: IexpiryDate, CustomFormat: T("d MMMM yyyy"))</span>

  • ... Posted by Niranjan Singh Posted 02/02/2017 12:53 PM [http://www.niranjankala.in]

    Hi Guys, I thinks this article missing lots of information. Missing images making it irrelevent.. Regards, Niranjan

Leave a comment