As we saw in the previous chapter, getting a basic Orchard site up and running is a pretty low friction process. Without having to write a single line of code, we were able to create new pages with dynamic content and reasonably advanced functionality. However, we also saw that the content we displayed has a default rendering, which we can’t fully customize through the admin tools.
Fortunately, Orchard does provide functionality to allow us to alter this rendering. In fact, with minimal effort Orchard gives us great control over how we display content. Virtually every piece of data that gets rendered by Orchard (collectively known as shapes) may be customized by overriding its default template. In this chapter, we’ll learn about the conventions and tools that make this process relatively straightforward.
As it exists now, the bio projection page simply takes each field in
our Bio
content type and renders its
label and value one after the other with each on its own line (see Figure 3-1). It would be better if our
content read a little more naturally, such as “John Zablocki was born in
Wethersfield, Connecticut, and plays guitar for Daisy’s Gone.” The
label-value rendering might be sufficient for a product listing, but is
less suited for our biography listing.
Ultimately, we’ll create a template for rendering Bio
content items in a projection page (or any
other container). However, to demonstrate how Orchard alternates work,
we’ll start at a higher level and change the way all content is displayed
when it’s rendered in a container (a projection in our case).
Orchard content is rendered differently based on its usage. For
example, when content items such as those created from our Bio
type are added to a projection page, their
summary display type is used. When a full content item is rendered (i.e.,
an individual biography), then the detail display type is used.
If you open up the Themes
project in your Orchard solution (in the “Themes” solution folder)
you’ll find a directory named TheThemeMachine, which is the default Orchard
theme. The styles associated with this theme are kept intentionally
simple, because this theme is generally used as a starting point for
building new themes. In the next chapter, we’ll create a new theme from
scratch, but for now we’re going to work within this one.
The first thing to do is expand the Views directory. There you’ll see six Razor (.cshtml) files already in this folder. These files are all used by the “TheThemeMachine” theme. We won’t explore these files now as we’ll discuss them in the next chapter.
Add a file to this directory, named Content.Summary.cshtml. If you clear the
contents of this new Razor file, save it and then refresh the “Bios”
projection page, what you’ll see (Figure 3-2) is that the
Bio
item summaries have disappeared.
Also notice that the “Upcoming Events” are no longer appearing in the
projection widget that we added to the AsideSecond
zone.
The Themes project was not created using an ASP.NET MVC Visual Studio project template, therefore the usual MVC context menu shortcuts are absent from this project (i.e., “Add View”). Instead, I use Add→New Item and select “HTML Page” from the list of web templates. This action requires manually setting the .cshtml extension.
As you might suspect, Orchard found our new alternate template and
used that file to render the summary display for our Bio
items. You might also have realized that
like our “Upcoming Events,” the Event
and Blog Post
items have also
disappeared. You can verify this by clicking on the “Events” or “Blog”
tabs. What this change is starting to demonstrate is how Orchard
performs the task of “shape rendering.”
There are several file and directory naming conventions used by Orchard to determine which template should be used to render a particular piece of content. In our example, creating a view named Content.Summary instructed Orchard to use this template for all content shapes rendered with the summary display type. These views are known as “alternate templates.”
Again, content items in a projection page are rendered using the “summary” display type. We could edit our alternate template to add the header (title) and content (everything else) back in. To do so, we need to add only two lines of code. For illustrative purposes only, we’ll also change the background color of the content:
<div style="background-color:#c0c0c0;"> @Display(Model.Header) @Display(Model.Content) </div>
If you save the template and refresh the bio, event, and blog projection pages, you’ll see that the content is again being rendered as before, but now with a gray background. You’ll also see our “Upcoming Events” widget being rendered the same way.
To change this alternate template so that only Bio
content items are affected by the
customization, you simply have to rename the file to Content-Bio.Summary.cshtml. Orchard will
parse the token after the hyphen and match it to a content type id. In
this case “Bio” was the default content type id associated with the
Bio
content type when we created it.
With this change, Event
and Blog Post
items are no longer rendered with a
gray background within their respective containers.
You could also rename the file to Content-17.Summary.cshtml, where “17” is the unique identifier of a particular piece of content (my bio in my case). After renaming the file, if you refresh the “Bios” page you’ll see that only the bio summary specified in the filename has been grayed (Figure 3-3). This item level customization might be useful for calling out a particular item in a list, such as a special event.
Let’s rename our alternate template back to Content-Bio.Summary.cshtml so that we can return to our original goal, which was to render our bio listings with more natural language. To achieve this goal, we’re going to create a simple template that will display values from our content type in an order that we define.
Our template first defines a simple Razor function splitName
for convenience. We’re going to take
the shortcut of parsing our first name out of the title, as opposed to
creating a separate field for this value in our type definition. We’ll
then check whether a profile picture has been set. If it has, we’ll
display it:
@{ Func<string, string[]> splitName = (name) => name.Split(' '), } <div style="float:left;margin-bottom:10px;"> @{ var fileName = Model.ContentItem.Bio.Headshot.FileName; if (! string.IsNullOrEmpty(@fileName)) { <img src="@Url.Content(fileName)" alt="@Model.Title" style="float:left;margin-right:10px;" /> } } <a href="@Model.ContentItem.AutoroutePart.DisplayAlias"> @Model.ContentItem.TitlePart.Title </a> plays @Model.ContentItem.Bio.Instruments.Value.ToLower() for Daisy's Gone. <p /> @splitName(Model.ContentItem.TitlePart.Title)[0] is originally from @Model.ContentItem.Bio.Birthplace.Value and currently lives in @Model.ContentItem.Bio.LivesIn.Value. </div>
Next we display a link to the full bio page. We set the href
property of the anchor tag by accessing
the Bio
content item’s Autoroute
content part, which has the URL slug
for permalinks. We then construct a sentence that summarizes a little
information about each musician. In this sentence, we’ll access both the
Bio
type’s fields and its Title
part to fill in the template
values.
The important thing to note is that if you want to reference
fields from a content item in your templates, you’ll need to use a
dynamic expression that is generally of the form Model.ContentItem.{ContentType}.{FieldName}.Value
.
Other elements such as the title require first accessing its content
part. Later on in this chapter, we’ll see how to discover these values
using the Orchard tools.
We just fixed our bio summary templates so that the content wouldn’t just dump out labels and values for each property. However, when we click through to an individual bio, we can see that the detail view for our page looks like our summary did before our customization.
To get our bio details and summary views more in line, we’ll add a
new alternate template. Start by creating a new Razor file named
Content-Bio.cshtml in the Views directory in “TheThemeMachine.” This
new template will be only a slight variation of the Bio
item summary template we just
created:
<h1>@Model.ContentItem.TitlePart.Title</h1> @{ var fileName = Model.ContentItem.Bio.Headshot.FileName; if (!string.IsNullOrEmpty(fileName)) { <img src="@Url.Content(fileName)" alt="@Model.Title" style="float:left;margin-right:10px;" /> } } Plays @Model.ContentItem.Bio.Instruments.Value.ToLower() for Daisy's Gone.<p/> Originally from @Display(Model.ContentItem.Bio.Birthplace.Value).<p /> Currently lives in @Model.ContentItem.Bio.LivesIn.Value.<p /> @Html.Raw(Model.ContentItem.BodyPart.Text)
It’s also worth noting that if we removed the summary alternate template, this new template would then be used to render summary listings as well since the name of this file doesn’t specify a display type (i.e., Summary or Detail). We could also have (and probably should have) named this file Content-Bio.Detail.cshtml to remove the potential impact on summary displays.
Since shapes are dynamic
types, it’s occasionally helpful to attach the Visual Studio debugger
to your Orchard application to inspect the object being bound to your
view. You can set a break point in a Razor file in a code block, which
is any executable line between a @{ }.
Like our bios in our bio listing, events in our events listing are rendered one property at a time with labels and values occupying one line each. Arguably, this display is appropriate for events, so we won’t create an alternate template for event summaries. However, we do want to adjust the order in which fields are rendering. Figure 3-5 shows that the location appears before the body field, while the event date is after. We’ll move both of these fields ahead of the HTML body field.
To rearrange the fields, we’ll use a special XML file named Placement.info that lives in the root of a template directory. This file defines rules for rendering content items, parts, and fields. “Place rules” allow module developers and template designers to provide preferred layout ordering for content pieces. We’ll modify the Placement.info file in the root directory of the “TheThemeMachine” theme by replacing its content entirely with this XML:
<Placement> <Match ContentType="Event"> <Match DisplayType="Summary"> <Place Fields_Text="Content:before"> </Place> <Place Fields_DateTime="Content:before"> </Place> </Match> </Match> </Placement>
The Match
element will limit
the placement rule so that it affects only items of type Event
that are shown with the summary display
type. The Place
rules that follow the
Match
specify that the text and date
time fields will appear before the Content
zone, where the HTML body is placed.
If we want to force the event date to render after the location, we
could provide relative numeric values instead of “before” or
“after”:
<Place Fields_Text="Content:2" /> <Place Fields_DateTime="Content:3" />
We also want to jazz up our location field so that it renders a
link to Bing Maps for that particular location. To do so, we’ll create
an alternate template for just that field. This is different from what
we did with Bio
content, where the
alternate template affected an entire item’s rendering.
Create a new directory named Fields under the Views directory in the “TheThemeMachine” theme. To that new directory, add a file named Common.Text-Event-Location.cshtml (prefixing this file with Fields would make the new directory unnecessary). The body of this new template will simply supply the value of our event’s location to the “q” parameter used by Bing Maps when it performs a search:
@Model.Name: <a href="http://www.bing.com/maps/[email protected]"> @Model.Value </a><p />
If you refresh the event listing, you’ll now see the location field is wrapped in a link to Bing Maps. If you click through to view the actual event, you’ll see that the location field is also linking to Bing Maps (Figure 3-6). Our field alternate applies to both summary and detail displays.
There is still one last problem we want to fix. The ordering of
the event fields in the details display type is not the way we want it.
The event date is rendering below the content as was previously the case
with event summary displays (Figure 3-6). To fix this,
we’ll simply remove the Match
tag
from Placement.info where we’d
previously restricted the display rule to summary only:
<Placement> <Match ContentType="Event"> <Place Fields_DateTime="Content:1" /> <Place Fields_Text="Content:2" /> </Match> </Placement>
While for some shapes it’s easy enough to figure out the correct
naming for alternate templates based on the documented rules, it’s not
always obvious. Fortunately, there’s the Shape
Tracing
module to help navigate the complex hierarchy of
shapes that compose a page. Shape
Tracing
is installed but disabled when you create a site with
the “Default” recipe. It can be enabled on the admin dashboard under
Modules→Features. It’s listed under the
“Designer” category.
After you enable the Shape
Tracing
module, across the bottom of each page of your site
you’ll see a small bar with a small square icon all the way to the
right. Clicking it will bring up the tools for outlining the shapes on a
page.
Shape Tracing
is a
JavaScript-based utility that provides information about how zones and
the shapes contained in these zones are rendered. As you move your mouse
around the page, the Shape Tracing
tool highlights content pieces such as zones, widgets, content parts,
and fields.
To demonstrate the utility of the Shape
Tracing
module, we’ll create an alternate template for the
content of the “Body” field on our home page. We’re going to add an
image to the content and float it to the left of the text. We could
use the HTML editor to add an image, but this template will give us a
little more control over layout.
With the Shape Tracing
module
activated (click the little square), mouse-over the Content
zone. Click when one of the
paragraphs is highlighted. The shape tracing console will display
information under the “Shape” panel (Figure 3-7).
If you expand the “Alternate” tree under the “Shape” panel, you’ll see three possible options for creating an alternate template. Next to each option is a “Create” button that you can click to have the tool generate an alternate template for you in your current theme. Click “Create” to generate the Parts.Common.Body-11.cshtml. Recall that the number in the filename is the unique content ID of a content item and yours will vary.
In order to navigate your site normally again, you must click
the dash icon in the upper-right corner of the Shape Tracing
tool, to turn off its
functionality.
If you use the Shape Tracing tool to create new alternate templates, these files won’t be added to the project automatically. You’ll need to click “Show All Files” in Visual Studio’s Solution Explorer to see them. You can then select “Include in Project” from the context menu.
After you click to create this new alternate template, back in
the Views directory of the
“TheThemeMachine” theme, you’ll find the new file. By default its code
just renders the HTML
property of
the Model
property of the page. The
Shape Tracing module creates alternate templates based on current
template for that piece of content.
Now we’ll add the new image for our home page. Create a new media folder by clicking Media→Add a Folder. Name the new folder “Art” and click “Save.” Click into “Art” and then click “Add Media” and upload an image.
After it’s been uploaded, click the filename to get its path
(copy the “Embed” value). Then back in Visual Studio, open Parts.Common.Body-11.cshtml and add an HTML
img
tag with the source set to this
new image. We’ll render the saved HTML (which is simply the “Body”
content) next to our Daisy’s Gone logo:
<img src="/Orchard/Media/Default/Art/daisyslogo.png" style="float:left;margin-right:10px;" /> @Model.Html
We also could have named the template Parts.Common.Body-Page.cshtml. This naming
would have resulted in all pages with a Body
part being affected. For our site, that
would have meant the home, contact, gallery, and about pages would all
include the new image. Even the “News and Notes” HTML widget would be
affected.
The Shape Tracing
module also
provides us with a way to figure out object hierarchies for our
template alternates. If you wondered how the expression Model.ContentItem.AutoroutePart.DisplayAlias
was discovered for use in our bio templates, it was the Shape Tracing
module.
Hover again over a piece of content while the Shape Tracing
module is enabled, but this
time click the “Model” tab in the console. You’ll see a tree (Figure 3-10) that contains information
about the ContentItem
property of
the view’s Model
property.
You’ll see details of content parts and fields. As you click
through the tree, the expression to access these properties appears
just below the tabs. Simply copy that expression into your alternate
template. For example, you could highlight an event in our “Upcoming
Events” widget and click through the model to find the location
expression @Model.ContentItem.Event.Location
.
The Shape Tracing
module can
also be used to see the placement rules for a piece of content, the
current template used to display that content and the actual HTML
rendered for that content. It’s an incredibly powerful tool to have at
your disposal. You’ll use it frequently while customizing the display
of content on your site. Just don’t forget to disable the module if
you’ve enabled it on your live site for testing.
We’ve seen in this chapter that Orchard provides extensive ways to customize content. We can provide alternate renderings for full content items or individual fields within our content. Our alternates were somewhat utilitarian in that they offered improved and new functionality or changed the way something rendered. The second part of customizing content in Orchard is to create striking visual changes to the way content is rendered. We’ll learn how to make such changes in Chapter 4.