As I said in my earlier post, this series is about creating a real world site with Orchard CMS, I’m not covering using Orchard to manage the site, just the technical development parts. All of the code is available on codeplex here : http://orchardsamplesite.codeplex.com/
Preamble
In the earlier parts of this series we created a basic theme, and a widget that will display latest twitter feeds (actually, that widget just displays canned results but if you look in the “final” directory in the source, you’ll find the finished working version of this widget). In this post I want to look at enhancing the theme somewhat as there are a few subtleties at the moment that aren’t right with how we want this theme to function.
If you’re following along, by now you should have added some content to your site, on the home page you should have;
- A html widget that shows the featured product content (just mocked up at the moment)
- A html widget with some “about us” content in the left hand column
- The twitter widget in the left hand column
- A blog created with a couple of dummy posts
- A recent blog posts widget in the centre column
- An additional html widget with some “support” content in the right hand column.
Like this;
But this isn’t quite how we want it. It should look more like this;
So there are 5 key things we need to sort out;
- Page titles
- Getting rid of it on the homepage
- Restyling it everywhere else
- Removing the publishing metadata (date) on pages
- Using a filter provider to indicate we’re rendering the homepage
- To make some discreet changes in the main layout template without having a separate template just for the homepage.
- Making the widget titles stand out more (I believe this is what they call “making it pop” <<snigger>>)
- Reformatting the recent blog posts widget on the homepage
- Improving our main blog page to show full articles
Part 4.1 – sorting out page titles
Page titles in the theme at the moment serve their purpose, but they aren’t quite right for us in a number of ways. These are;
- They show publish date/time with every page title, which on the blog list page can be a little cumbersome in particular.
- We don’t want the page title on the homepage at all, it’s a little ungainly.
- We want them to “pop” more ;)
What we want to achieve is to change the way page titles are displayed from this, as the theme is presently configured;
to this… yes, I know, it’s only a subtle change, but <sarcasm>it “pops” now don’t you think</sarcasm>
The first thing we want to look at is placement.info – placement.info is the theme writers friend, I suggest you go off an read about it in the documentation, but suffice it to say it allows you to send shapes you don’t want to oblivion or to provide alternate templates (or wrappers) in certain conditions. You can specify criteria, such as on a particular page, when rendering a particular content type, I want to hide the metadata shape, or I want to render it with an alternative template name and so on. We’re going to use placement.info to get rid of the title and metadata on the homepage and get rid of the metadata on other pages.
In your theme project, create a file named “Placement.info” in the root of the project, and fill it thus;
1: <Placement>
2: <!-- Remove the page title from the homepage -->
3: <Match Path="~/">
4: <Place Parts_RoutableTitle="-"/>
5: </Match>
6:
7: <!-- Remove metadata part from all pages and from blogs -->
8: <Match ContentType="Page">
9: <Place Parts_Common_Metadata="-"/>
10: </Match>
11: <Match ContentType="Blog">
12: <Place Parts_Common_Metadata="-"/>
13: </Match>
14: </Placement>
Lines 3,4,5 create a rule that whenever the homepage is being rendered, it should take the shape named Parts_RoutableTitle and send it to a local zone named “-“. This zone doesn’t exist, it’s used to send the shape rendering to purgatory. The routable title shape usually includes the metadata shape also (Parts_Common_Metadata – the shape with the published date/time on it), so by removing the entire title shape we also get rid of the metadata on the homepage.
Lines 7-10 remove the Parts_Common_Metadata shape from any page that is rendered. A page is of course a content type, and the match rule reflects this.
Similarly on lines 11-13 we tell orchard to remove the metadata shape from the blog list pages. Note this is not the blog post pages, where we want to keep that meta data to show when the post was published, blog post pages have a content type of BlogPost, so these lines will only affect the overall blog listing page.
So that part was simple enough, we’ve removed the bits we don’t want, so we now need to reformat the bits we wanted to change; Before we go on, ensure you understand the following bits of the orchard documentation, which explain things more clearly than I can;
- http://www.orchardproject.net/docs/Accessing-and-rendering-shapes.ashx
- http://www.orchardproject.net/docs/Alternates.ashx
In summary, a shape which is added to the pipeline for rendering will have a bunch of templates that you could override to change how it will be rendered. For instance, to change a page title with a shape name of Parts_RoutableTitle, you could just create a new razor file in your template’s view folder called Parts.RoutableTitle.cshtml and this will change every page title throughout your site. Alternates are also available, so you could create Parts.RoutableTitle-Page.cshtml and this will be the template used whenever the Parts_RoutableTitle shape is rendered within a content type of Page. For a full list of alternates, use the shape tracing feature of orchard 1.1 (and enable the url alternates feature for more alternates out of the box);
So, because we want to change the title on every page (except the homepage where we’ve removed it), we can just create Parts.RoutableTitle.cshtml in our views folder as follows;
<h1 class="page-title">@Model.Title</h1>
That’s all – if you’re wondering how to know the “Title” property is available on this dynamic, again, take a close look at the shape tracing feature, specifically the model tab (Anyone else want to send free beer to whomever wrote this little nugget of gold???)
All we then need is the H1.page-title class in our stylesheet;
h1.page-title{ font-size: 2em; border-bottom: 1px dotted black; margin-bottom: 8pt; padding-bottom: 2px; }
We should now be in a position where the title and metadata is removed from the homepage, the metadata is removed from the pages we don’t want it and our titles “pop”. Let’s go back to the homepage.
Part 4.2 closing the gaps on the homepage with a filter provider
Notice that between the promo content and the widget areas there is still some blank content. In fact if you inspect the rendered source you will find this;
<div id="layout-body" class="zone"> <div id="zone-body" class="without-sidebar"> <div class="zone zone-content"> <article class="content-item page"> <header> </header> </article> </div> </div> </div>
Which, in our layout.cshtml corresponds to;
@if (displayContent || displayBodySideBar) { <div id="layout-body" class="zone"> <div id="zone-body" class="@bodyClasses"> @Display(Model.Content) </div> @if (displayBodySideBar) { <div id="zone-body-sidebar"> @Display(Model.BodySideBar) </div> } </div> }
Hmmm – so our Model.content is empty (so displayContent should have been false) but it’s clearly not null, so it’s rendering this whole section which we don’t really want if we’re on the homepage and I want that gap closed. I could create a separate layout for the homepage using url alternates, but to be honest this is so trivial I’d sooner just be able to check in the template code if this is the homepage and if so, don’t display the entire body content section, even if there IS content in there. We can use a FilterProvider for just this purpose;
Create a class file in your theme called LayoutFilter.cs with the following code;
using System.Web.Mvc; using Orchard; using Orchard.Mvc.Filters; namespace SampleSite { /// <summary> /// This filter just adds an IsHomepage flag to the Layout shape if the page /// being rendered is the homepage. We check this in the layout to determine /// whether to render the content area or not (this theme is used to just render /// widgets in zones on the homepage) /// </summary> public class HomepageLayoutFilter : FilterProvider, IResultFilter { private readonly IWorkContextAccessor _wca; public HomepageLayoutFilter(IWorkContextAccessor wca) { _wca = wca; } public void OnResultExecuting(ResultExecutingContext filterContext) { var workContext = _wca.GetContext(); var routeValues = filterContext.RouteData.Values; if (((string)routeValues["area"]) == "HomePage") workContext.Layout.IsHomepage = true; } public void OnResultExecuted(ResultExecutedContext filterContext) { } } }
This filter gets discovered automatically and called when a page render is executing. It gets an IWorkContextAccessor injected into it, which we can use to manipulate the shapes already in the shape tree. Our OnResultExecuting method gets called and we determine if the MVC area we are in is for the HomePage and if it is, we inject a property into the Layout shape that flags that this is the homepage. We then just need to take that into account in our layout.cshtml by changing this line;
var displayContent = (Model.Content != null);
to this;
var displayContent = !(WorkContext.Layout.IsHomepage == true);
Now when the homepage is rendered, that slightly annoying gap should close without having to create a whole separate template.
Part 4.3 – adding more distinction to the titles of widgets
Presently the widget titles on the homepage (and the rest of the site) look a little lost in the overall design – they look a little anaemic don’t you think;
We want them to look more like this;
Which is extremely simple, we just override the wrapper markup for widgets by creating “Widget.Wrapper.cshtml” in our views folder with the following code;
@* This overrides the default widget wrapper *@ @using Orchard.ContentManagement; @using Orchard.Widgets.Models; @{ var title = ((IContent)Model.ContentItem).As<WidgetPart>().Title; var tag = Tag(Model, "article"); } @tag.StartElement @if (HasText(title) || Model.Header != null) { <header> @if (HasText(title)) { <h1 class="widget-title">@title</h1> } @Display(Model.Header) </header> } @Display(Model.Child) @if (Model.Footer != null) { <footer> @Display(Model.Footer) </footer> } @tag.EndElement
This is very similar to the default widget wrapper, except we’re using H1 for the title with a CSS class of widget-title, we define this in our stylesheet as;
h1.widget-title{ font-size: 1.1em; font-weight: bold; border-bottom: 1px dotted black; margin-bottom: 8px; padding-bottom: 2px;}
And that gives us our new widget titles. We’ll be doing the same thing in another post later to add the twitter icon to our widget title for the twitter component (see the original mock up in the introductory post)
Part 4.4 - A less cluttered look to the recent blog post summaries
As you can see below, the amount of information we’re presenting on the homepage for a quick summary of posts is a little much;
We’re going to aim for something a little more succinct;
Notice we’ve removed the tags, removed the comment count, make the title stand out a little more and also added who the author was. To achieve this we’re going to use the url alternates feature that can be found in the designer tools module – get it from the gallery and install it;
Ensure you have enabled the url alternates feature;
This feature provides some additional alternates for shapes based on url, we’re going to use an alternate for how to render blog post shapes, in summary form, on the home page. With this feature active, we can now create a shape template called Content-BlogPost-url-homepage.Summary.cshtml in our views folder. This rather long template name indicates that it will be called when rendering a summary display of a BlogPost content item when on the homepage url. The content of the file should be;
1: @*
2: This template changes the way blog post summaries
3: are rendered on the homepage (Requires url alternates feature)
4: *@
5: @using Orchard.Core.Routable.Models
6: @using Orchard.ContentManagement.ViewModels
7: @using Orchard.ContentManagement
8: @using Orchard.Core.Common.Models
9:
10: @{
11: ContentItem item = Model.ContentItem;
12: RoutePart rpItem = item.As<RoutePart>();
13: BodyPart bpItem = item.As<BodyPart>();
14:
15: string linkUrl = Url.ItemDisplayUrl(item);
16: }
17:
18: <h4><a href="@linkUrl">@rpItem.Title</a></h4>
19: <div class="publishinfo">@Model.ContentItem.CommonPart.PublishedUtc by @Model.ContentItem.CommonPart.Owner.UserName</div>
20: <div>
21: <p>@Html.Raw(Html.Excerpt(bpItem.Text, 200).ToString())</p>
22: </div>
Nothing much to explain here, we get the content item that is being rendered (the blog post) and get it’s Route content part and Body content part, then render it how we wanted it. EDIT: See the comment from Bertrand LeRoy on the part 3 post about working with dates.
Part 4.5 - A cleaner and fuller blog page
The final part of this (what has turned into a length) post is regarding the blog page. Most blogs display the full article of the most recent posts on the main blog page rather than just a summary but ours renders like this;
For our site, I’d sooner see the full article;
To achieve this, we look back to our old friend, placement.info, adding the following match rule and placements.
<!-- Remove comment counts from blog posts and set the summary blog post body alternate --> <Match ContentType="BlogPost"> <Place Parts_Comments_Count="-"/> <Match DisplayType="Summary"> <Place Parts_Common_Body_Summary="Content:5;Alternate=Parts_BlogPostSummaryBody"/> </Match> </Match>
Notice we have nested match rules – the outer rule is matching against blog posts content type, the inner rule is only for when the display type for this content type is summary. We’re obviously getting rid of the comments count in general, but then in the summary rule, we’re also telling orchard to look for an alternate named Parts_BlogPostSummaryBody, which it will use when the match conditions are met and when it’s rendering a shape named Parts_Common_Body_Summary. Remember from the documentation that content is composed of multiple parts - the blog post is therefore made up of multiple parts and one of those is the body part (Parts_Common_Body), which when it’s rendered on the blog list page will render in summary form (Parts_Common_Body_Summary)….
So, this rule and placement is saying when I’m rendering a blog post and when I’m rendering it in summary form, and when the shape I’m rendering is the Parts_Common_Body_Summary shape, look for an alternate named Parts_BlogPostSummaryBody, which in turn translates to a template file of Parts.BlogPostSummaryBody.cshtml which should be in your views folder and have the following content;
@Model.Html
That’s it! Now, when your blog page renders it will show the entire blog post.
Final words
You should now have a site that looks more like our original design;
As a final word, forgive any inaccuracies in this series, I’m sure there will be some, I’ve only been looking at orchard now for a matter of days, I’m loving it’s flexibility and I’m sure I’ve made some mistakes along the way, but I do hope this gives you a steer in the right direction when theme-ing your sites. For the rest of the series, I’m planning on moving away from the skinning side of Orchard to look at some of the other cool things you can achieve. Don’t hold me to this, but the next parts I’m thinking will be something along the lines of;
- Creating custom content types
- Defining the product listing
- Defining the product page and adding social features like voting
- Adding screenshot library to the product page
- Creating sub-navigation
- Creating a widget that will allow navigation within sections of your site
- Relating content
- Creating a widget to find content on your site that may be related to the current content being viewed.
- Basic shopping, advanced widgets and controller based content
- This will be a multi-part post I think covering implementing basic shopping basket functionality
- Creating a widget that renders initially as part of the normal rendering pipe and then updates with AJAX calls to a custom controller.
- Implementing a custom controller to provide complete rendering control – to display our shopping basket – yet keeping the rendering themeable.
As always, the source is in the part-04 folder on code plex : http://orchardsamplesite.codeplex.com/
See you soon! Tony.
Please send the beer to Sébastien Ros, 1 Microsoft Way, Redmond WA 98052 :)
ReplyDeleteIf you wrap the widget titles in h1 elements and the page title is also in a h1, aren't you going to end up with a bunch of h1 tags on the same page? Shouldn't there be a set of widget wrappers to determine which gets h2, h3, etc..?
ReplyDeleteI assumed the widget titles would be set as h2 since it's "acceptable" to have multiple sub headings not h1s.
Sweet tutorial though! Thanks!
I'm just a newb asking dumb questions... i didn't know you could have multiple header elementss with their own h1 tags.. found my answer here: http://html5doctor.com/the-header-element/
ReplyDeletepretty close to launching my first orchard site thanks to your posts! So thanks again!
I have read and reread this - it works, I am starting to understand ...
ReplyDeleteabsolutely brilliant !
Your article helps me a lot too!
ReplyDeleteThank you!
You make the world better!)
Hello I would like a theme for my site in Orchard.
ReplyDeleteI have a site template and css. I downloaded from the internet.
My site has an integrated blog, and contact form.
Can you help me? What is the cost of your service.
I live in Brazil, and here I have not found anyone that works with that platform.
grateful
how about adding custom class in the body tag?
ReplyDeleteYou'll need to add Document.cshtml to your theme (copy from Themes/SafeMode/Views) as it controls the base layout (if there's a Document.cshtml in your theme, it will use that instead of the global one).
ReplyDeleteHowever - why do you need a custom class on the body tag though?? Just style BODY directly....
For some reason the 'adding more distinction to the titles of widgets' part gets rid of my nav menu - basically about and blog titles on it just turn back and moved to the left, and home is moved down.
ReplyDeleteAny ideas why? done it about 4 times now and the same every time. Nothing is wrong. But I do get a red underline in the code for the widgets and widgets.part aren't referenced in the following code:
@using Orchard.Widgets.Models;
@{
var title = ((IContent)Model.ContentItem).As().Title;
!!!!!!
anyone else had this problem?