Part 4: Creating the standard page – Create an EPiServer site from scratch

Posted on December 13, 2009 by Frederik Vig in EPiServer

In this part we’re going to create the standard page. We’ll use this page type for most of our site, from news to other stuff. As always start by taking a look at what you’re about to implement: style.html. It contains all the common scenarios that editors might use, tables, lists, quotes, links, images etc.

After taking a look at what we’re about to create, we can start planning. Special information includes: title, author name, categories, the main text and a sub menu.

Lets start by creating a new class with the name StandardPage, derive from BasePageData, and add the PageType attribute to the class declaration.

using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] Standard page ",
        Filename = "/Templates/Jungle/Pages/Page.aspx",
        SortOrder = 1010)]
    public class StandardPage : BasePageData
    {
    }
}

Lets now add the four properties: Heading, MainBody, ListImage, and FeaturedNewsImage.

using EPiServer.Core;
using EPiServer.Editor;
using EPiServer.SpecializedProperties;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] Standard page ",
        Filename = "/Templates/Jungle/Pages/Page.aspx",
        SortOrder = 1010)]
    public class StandardPage : BasePageData
    {
        [PageTypeProperty(Searchable = true, Type = typeof(PropertyString))]
        public virtual string Heading
        {
            get;
            set;
        }
 
        [PageTypeProperty(
            EditCaption = "Main body",
            Searchable = true,
            Type = typeof(PropertyXhtmlString),
            LongStringSettings = (
                EditorToolOption.All ^
                EditorToolOption.Font))]
        public virtual string MainBody
        {
            get;
            set;
        }
 
        [PageTypeProperty(EditCaption = "List image", Type = typeof(PropertyImageUrl))]
        public virtual string ListImage
        {
            get; set;
        }
 
        [PageTypeProperty(EditCaption = "Featured image", Type = typeof(PropertyImageUrl))]
        public virtual string FeaturedNewsImage
        {
            get;
            set;
        }
    }
}

Next up is creating the page template in Visual Studio. Right-click Pages, and choose New Item. Choose Web form and give it a name of Page.aspx. Delete all the markup (everything except the first line), and set MasterPageFile to Site.Master.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.Page" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>

Open up your code-behind file and derive from TemplatePage<StandardPage> so that we can use our four properties (remember to add using PageTypeBuilder.UI;). Also remove the Page_Load method.

If we build (ctrl + b), go back to edit mode, and create a new page based on our standard page type, we should get something like this.

Something is seriously wrong – this is not how our prototype looked at all. When opening up Firebug and inspecting the form element (surrounding the content), you’ll see some CSS rules that get triggered. They are on line 136 in Jungleland.css, for now just comment out the form and form p selectors.

/* form elements */
/*form {
	margin: 10px 20px 10px 20px; 
	padding: 15px 25px 25px 25px; 
	border: 1px solid #251a14;
	background-color: #130d0a;	
}
form p {
	border-bottom: 1px solid #221813; 
	margin: 0;
	padding: 13px 5px 8px 5px;		
	color: #fff;
}*/

Save, and go back to your browser, you should now see a better result. But still some problems with the quick search field at the top. Lets just hide that for now. Go to PageHeader.ascx and set the QuickSearch’s Visible property to false.

<Jungle:QuickSearch runat="server" Visible="false" />

There we go!

Main body

Create a new user control, MainBody.ascx. We’ll use it to hold the title, author, category, main text and date. The reason for putting this inside a user control and not directly into Page.aspx, is so that we can easily reuse the same functionality in other page types, and more cleanly separate things.

Copy <div class=”post”>, and everything inside it, into MainBody.ascx. Replace the text with the EPiServer Property control, and set the PropertyName to MainBody. Add a new extension method for getting the heading of the page and using the PageName as a fallback.

public static string Heading(this PageData page)
{
    if (page.PageLink == null || page.PageLink.ID < 1)
    {
	return string.Empty;
    }
 
    if (page.IsValue("Heading"))
    {
	return page["Heading"].ToWebString();
    }
 
    return page.PageName.ToWebString();
}

We can also use the Author extension method that we created in the previous post. You should now have something like this.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MainBody.ascx.cs" Inherits="Jungle.Templates.Jungle.Units.MainBody" %>
<%@ Import Namespace="Jungle.Templates.Jungle.Util"%>
<div class="post">
    <h2><%= CurrentPage.Heading() %></h2>
    <p class="post-info">
        Posted by <a href="index.html"><%= CurrentPage.Author() %></a> | Filed under <a href="index.html">templates</a>,
        <a href="index.html">internet</a></p>
    <EPiServer:Property runat="server" PropertyName="MainBody" DisplayMissingMessage="false" EnableViewState="false" />
    <p class="postmeta">
        <a href="index.html" class="readmore">Read more</a> | <a href="index.html" class="comments">
            Comments (3)</a> | <span class="date">August 20, 2009</span>
    </p>
</div>

One thing that I find annoying is having to add <%@ Import Namespace="Jungle.Templates.Jungle.Util"%> everywhere I’m using something from our util classes. Lets add the namespace to our web.config file instead. We can do the same with EPiCode.Extensions.

<pages validateRequest="false" enableEventValidation="false">
    <controls>
    ...
    </control>
    <namespaces>
	...
	<add namespace="Jungle.Templates.Jungle.Util"/>
	<add namespace="EPiCode.Extensions"/>
    </namespaces>
</pages>

Before testing this new functionality, we need to register MainBody with Page.aspx.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.Page" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
<%@ Register TagPrefix="Jungle" TagName="MainBody" Src="~/Templates/Jungle/Units/MainBody.ascx" %>
 
<asp:Content runat="server" ContentPlaceHolderID="MainContentRegion">
    <Jungle:MainBody runat="server" />
</asp:Content>
 
After testing and making sure we didn't break anything, we can continue implementing. I've added the date of the article and its categories.
<pre lang="csharp">
<div class="post">
    <h2><%= CurrentPage.Heading() %></h2>
    <p class="post-info">
        Posted by <%= CurrentPage.Author() %>| Filed under <%= CurrentPage.GetCategoryNamesSeparated(",") %></p>
    <EPiServer:Property runat="server" PropertyName="MainBody" DisplayMissingMessage="false" EnableViewState="false" />
    <p class="postmeta">
        <span class="date"><%= CurrentPage.StartPublish.ToString("dd MMMM yyyy")%></span>
    </p>
</div>

The GetCategoryNamesSeparated extension method.

public static string GetCategoryNamesSeparated(this PageData page, string separator)
{
    if (page.PageLink == null || page.PageLink.ID < 1)
    {
	return string.Empty;
    }
 
    string result = string.Empty;
 
    int categoryCount = page.Category.Count;
    int categoryNumber = 1;
 
    CategoryList categories = page.Category;
    const string format = "{0}{1} ";
    foreach (var categoryId in categories)
    {
	var category = Category.Find(categoryId);
 
	if (category == null)
	{
	    break;
	}
 
	if (categoryNumber++ == categoryCount)
	{
	    result += string.Format(format, category.Name, string.Empty);
	    break;
	}
 
	result += string.Format(format, category.Name, separator);
    }
 
    return result;
}

After adding some dummy text in edit mode I get something like this.

Standard page with dummy text

Sub menu

I’ve in a previous blog post written about the PageTree control. That post describes it pretty well, so I’m not going to repeat it here. Instead I’ll just show you the code, and encourage you to check out the post for more information.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SubMenu.ascx.cs" Inherits="Jungle.Templates.Jungle.Units.SubMenu" %>
<EPiServer:PageTree ShowRootPage="false" runat="server" id="Menu">
    <HeaderTemplate>
        <div class="sidemenu">	
	        <h3>Sidebar Menu</h3>
    </HeaderTemplate>
	<IndentTemplate>
		<ul>
	</IndentTemplate>
 
	<ItemHeaderTemplate>
			<li>
	</ItemHeaderTemplate>
 
	<ItemTemplate>
				<EPiServer:Property PropertyName="PageLink" runat="server" />
	</ItemTemplate>
 
	<SelectedItemTemplate>
	            <EPiServer:Property CssClass="current" PropertyName="PageLink" runat="server" />
	</SelectedItemTemplate>
 
	<ItemFooterTemplate>
			</li>
	</ItemFooterTemplate>
 
	<UnindentTemplate>
		</ul>
	</UnindentTemplate>
	<FooterTemplate>
	    </div>
	</FooterTemplate>
</EPiServer:PageTree>
using System;
using EPiServer.Web.WebControls;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Units
{
    public partial class SubMenu : UserControlBase<BasePageData>
    {
        public MenuList MenuList
        {
            get;
            set;
        }
 
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            if (this.MenuList == null)
            {
                return;
            }
 
            this.Menu.PageLink = MenuList.OpenTopPage;
            this.Menu.DataBind();
        }
    }
}

I also had to update PageHeader.ascx.cs and add a public property so that I could access the main menu

public MainMenu MainMenu
{
    get
    {
	return Menu;
    }
}

We can now register SubMenu in Site.master. We also need to give PageHeader an id, so that we get access it from Site.master.cs.

I added code for setting SubMenu’s MenuList property in Site.masters OnLoad method

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
 
    if (SubMenu != null && PageHeader.MainMenu != null)
    {
	SubMenu.MenuList = PageHeader.MainMenu.MenuList;
    }
 
    var page = (PageBase)Page;
    Page.Title = string.Format(_title, page.CurrentPage.Property["PageName"].ToWebString(), TitleSeparator, EPiServer.Configuration.Settings.Instance.SiteDisplayName);
 
    this.HtmlElement.Attributes["lang"] = page.CurrentPage.LanguageBranch;
}

Build and test that everything works as expected. Also make sure that the functionality we created on the start page works as expected, by creating some dummy pages.

The start page with some dummy content

ShareIt

ShareIt is custom property that lets you easily share an article with social media sites like Twitter, Facebook etc. I already have a post with some background on ShareIt. Be sure to have a look at it.

I added a new property to the start page under the Site configuration tab. To be able to add properties to any other tab than information (default), you have to create a new class with the tab, and derive from PageTypeBuilder’s tab class.

using EPiServer.Security;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.Tabs
{
    public class SiteConfiguration : Tab
    {
        public override string Name
        {
            get { return "SiteConfiguration"; }
        }
 
        public override AccessLevel RequiredAccess
        {
            get { return AccessLevel.Edit; }
        }
 
        public override int SortIndex
        {
            get { return 30; }
        }
    }
}

Notice that I’ve placed SiteConfiguration.cs in a new folder called Tabs (under Templates and Jungle).

We can now add our property to the start page and place it under the SiteConfiguration tab.

[PageTypeProperty(
    Type = typeof(ShareIt),
    Tab = typeof(SiteConfiguration))]
public virtual string ShareIt
{
    get;
    set;
}

The code for displaying ShareIt in MainBody.ascx. Remember to define which social media sites you want to display under the site configuration tab on the start page.

<div class="postmeta">
    <EPiServer:Property runat="server" ID="ShareIt" PropertyName="ShareIt" />
    <span class="date"><%= CurrentPage.StartPublish.ToString("dd MMMM yyyy")%></span>
</div>

Notice that I updated <p class=”postmeta”> to <div class=”postmeta”>

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
 
    this.ShareIt.PageLink = PageReference.StartPage;
}

I also added some CSS code to Jungleland.css.

/* ShareIt */
.shareit {
    clear: both;
    float: right;
}
.shareit ul
{
    overflow: hidden;
    margin: 0;
    padding: 0;
    float: left;
    margin-right: 15px;
}
 
.shareit h4 {
    float: left;
    font-size: 12px;
    margin: 2px;
    margin-top: 3px;
    font-weight: normal;
    color: #91807F;
}
 
.shareit .showmore {
    font-size: 12px;
    color: #000;
    line-height: 20px;
}
 
.shareit li
{
    float: left;
    margin-left: 2px;
    margin-top: 2px;
    list-style: none;
}
 
.shareit li a
{
    text-indent: -9999px;
    display: block;
    width: 16px;
    height: 16px;
    background: url(/FV.ShareIt/Images/all-16x16.png) no-repeat top left;
}
 
.shareit .facebook
{
    background-position: 0 -85px;
}
 
.shareit .feed 
{
    background-position: 0 -102px;
}
 
.shareit .twitter
{
    background-position: 0 -357px;
}
 
.shareit .bebo
{
    background-position: 0 0;
}
 
.shareit .delicious
{
    background-position: 0 -17px;
}
 
.shareit .digg
{
    background-position: 0 -34px;
}
 
.shareit .email
{
    background-position: 0 -68px;
}
 
.shareit .google
{
    background-position: 0 -119px;
}
 
.shareit .linkedin
{
    background-position: 0 -136px;
}
 
.shareit .messenger
{
    background-position: 0 -153px;
}
 
.shareit .mixx
{
    background-position: 0 -170px;
}
 
.shareit .myspace
{
    background-position: 0 -187px;
}
 
.shareit .nettby {
    background-position: 0 -204px;
}
 
.shareit .netvibes
{
    background-position: 0 -221px;
}
 
.shareit .newsvine
{
    background-position: 0 -238px;
}
 
.shareit .pdf {
	background-position: 0 -255px;
}
 
.shareit .print {
	background-position: 0 -272px;
}
 
.shareit .reddit
{
    background-position: 0 -289px;
}
 
.shareit .share
{
    background-position: 0 -306px;
}
 
.shareit .stumbleupon
{
    background-position: 0 -323px;
}
 
.shareit .technorati
{
    background-position: 0 -340px;
}
 
 
.shareit .yahoo
{
    background-position: 0 -374px;
}
 
.shareit .yahoobuzz
{
    background-position: 0 -391px;
}

And the result.

ShareIt

RSS feed

To create RSS feeds we need to add a new page type that allows our editors to choose of what they wish to create a RSS feed of.

using EPiServer.Core;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(Name = "[Jungle] RSS feed", Description = "Source of a rss feed", Filename = "/Templates/Jungle/Pages/RssFeed.aspx", SortOrder = 1080)]
    public class RssFeedPage : BasePageData
    {
        [PageTypeProperty(UniqueValuePerLanguage = true, Searchable = true, Type = typeof(PropertyString), EditCaption = "Main heading", HelpText = "This heading will present the main title of this page, if this property is not set the page name will be used as a heading instead.")]
        public virtual string Heading
        {
            get;
            set;
        }
 
        [PageTypeProperty(UniqueValuePerLanguage = true, Searchable = true, Type = typeof(PropertyString), HelpText = "The description of the rss feed")]
        public virtual string Description
        {
            get;
            set;
        }
 
        [PageTypeProperty(UniqueValuePerLanguage = true, Searchable = false, Type = typeof(PropertyPageReference), EditCaption = "Rss source", HelpText = "RSS-source page")]
        public virtual PageReference RssSource
        {
            get;
            set;
        }
    }
}

RssFeed.aspx looks like this.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="RssFeed.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.RssFeed" %>
using System;
using System.Xml;
using EPiCode.Extensions;
using EPiServer.Core;
using EPiServer.Filters;
using Jungle.Templates.Jungle.PageTypes;
using Jungle.Templates.Jungle.Util;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Pages
{
    public partial class RssFeed : TemplatePage<RssFeedPage>
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
 
            Response.ContentType = "text/xml";
            Response.Clear();
            if (!PageReference.IsNullOrEmpty(CurrentPage.RssSource))
            {
                this.WriteRssDocument();
            }
 
            Response.End();
        }
 
        private void WriteRssDocument()
        {
            var doc = new XmlDocument();
            XmlNode outerNode = doc.CreateElement("rss");
            XmlAttribute uriInfo = doc.CreateAttribute("xmlns:dc");
            uriInfo.Value = "http://purl.org/dc/elements/1.1/";
            outerNode.Attributes.Append(uriInfo);
            doc.AppendChild(outerNode);
 
            XmlAttribute versionInfo = doc.CreateAttribute("version");
            versionInfo.Value = "2.0";
            outerNode.Attributes.Append(versionInfo);
 
            XmlNode channel = doc.CreateElement("channel");
            outerNode.AppendChild(channel);
 
            XmlNode title = doc.CreateElement("title");
            title.InnerText = CurrentPage.PageName;
            channel.AppendChild(title);
 
            XmlNode link = doc.CreateElement("link");
            link.InnerText = EPiServer.Configuration.Settings.Instance.SiteUrl.ToString();
 
            channel.AppendChild(link);
 
            XmlNode description = doc.CreateElement("description");
            description.InnerText = CurrentPage["Description"] as string;
            channel.AppendChild(description);
 
            var children = GetChildren(CurrentPage.RssSource);
            FilterForVisitor.Filter(children);
            foreach (var page in children)
            {
                var item = doc.CreateNode(XmlNodeType.Element, "item", null);
 
                XmlNode itemTitle = doc.CreateElement("title");
                itemTitle.InnerText = page.PageName;
                item.AppendChild(itemTitle);
 
                XmlNode itemLink = doc.CreateElement("link");
 
                itemLink.InnerText = page.GetExternalUrl();
                item.AppendChild(itemLink);
 
                XmlNode itemDescription = doc.CreateElement("description");
                itemDescription.InnerText = page.PreviewText(400);
                item.AppendChild(itemDescription);
 
                XmlNode itemGuid = doc.CreateElement("guid");
                itemGuid.InnerText = page.GetExternalUrl();
                item.AppendChild(itemGuid);
 
                XmlNode itemPubDate = doc.CreateElement("pubDate");
                itemPubDate.InnerText = page.Changed.ToString("r");
                item.AppendChild(itemPubDate);
 
                foreach (var category in page.Category)
                {
                    XmlNode itemCategory = doc.CreateElement("category");
                    itemCategory.InnerText = page.Category.GetCategoryName(category);
                    item.AppendChild(itemCategory);
                }
 
                channel.AppendChild(item);
            }
 
            doc.Save(Response.OutputStream);
        }
    }
}

We can now create a RSS feed of our news. Go to edit mode, create a new page of type [Jungle] RSS feed, choose the News container page.

News RSS feed

Last step is adding the RssContainer property to the start page.

[PageTypeProperty(
    EditCaption = "Rss container page",
    Type = typeof(PropertyPageReference),
    Tab = typeof(SiteConfiguration))]
public virtual PageReference RssContainer
{
    get; set;
}

Go to edit mode and the start page, and choose the Rss container page you created with the news. This will add the RSS feeds to the HTML head, which will allow for automatic detection of RSS feeds by RSS readers like Google Reader.

RSS feed link

<head>
    ...
    <link rel="alternate" type="application/rss+xml" href="/en/RSS/News-RSS/" title="News RSS" />
</head>

That’s it! The next post: Part 5: Creating the search page.

Related Posts: