Part 5: Creating the search page – Create an EPiServer site from scratch

Posted on December 16, 2009 by Frederik Vig in EPiServer

Today we’re implementing our sites search page. This will be a very standard search page with nothing fancy. Instead I’ve included some links that’ll help you extend this search page even more.

Quick search

We already have the markup for the quick search box placed in quicksearch.ascx, we’ll start by converting it to use ASP.NET controls instead.

Open up quicksearch.ascx. Lets replace the form element with a fieldset and give it the same id. Replace the input text element with an ASP.NET TextBox control and the input submit element with an ImageButton control. You should now have something like this.

<fieldset id="quick-search">
	<p>
	<label for="qsearch">Search:</label>
	<asp:TextBox CssClass="tbox" runat="server" ID="txtQuickSearch" ToolTip="Start typing and hit ENTER" Text="Search..." />
	<asp:ImageButton runat="server" CssClass="btn" ID="btnQuickSearch" CausesValidation="false" ToolTip="Search" ImageUrl="/Templates/Jungle/images/search.png" />  
	</p>
</fieldset>

Notice the CausesValidation=”false” on the ImageButton. The reason for this is that ASP.NET Validation controls that are used on the page would otherwise get triggered when the user uses our quicksearch button. Setting the CausesValidation property to false bypasses all validation.

Next we need to fix the label, and make sure that our users can use the enter key after filling out a search phrase. The reason that this doesn’t work out of the box in ASP.NET Web forms is that everything is placed inside one form, which means that only one button can be the default on. Though as you’ll see we can fix this for users that have JavaScript enabled.

<asp:Panel runat="server" DefaultButton="btnQuickSearch">
<fieldset id="quick-search">
	<p>
	<asp:Label runat="server" AssociatedControlID="txtQuickSearch">Search:</asp:Label>
	<asp:TextBox CssClass="tbox" runat="server" ID="txtQuickSearch" ToolTip="Start typing and hit ENTER" Text="Search..." />
	<asp:ImageButton runat="server" CssClass="btn" ID="btnQuickSearch" CausesValidation="false" ToolTip="Search" ImageUrl="/Templates/Jungle/images/search.png" />  
	</p>
</fieldset>
</asp:Panel>

For the enter key issue I added a Panel control and set its DefaultButton to the ImageButton’s ID. This will add the necessary JavaScript code for us. For the label I added a Label control and sat its AssociatedControlID to the TextBox’s ID. This will render as a HTML Label element and set the for attribute to the correct input’s id (ASP.NET Label controls normally render as span elements).

I also had to update the CSS code in Jungleland.css a little to make the selectors work with the slightly changed markup. I just removed the form prefix (form#quick-search).

/* header quick search */
#header #quick-search {
	position: absolute;
	top: 95px; right: 15px;
	z-index: 999999;
	padding: 0; margin: 0;
	border: none;
	width: 222px; height: 32px;
	background: url(../images/header-search.png) no-repeat;		
}
#header #quick-search p {
	margin: 0; padding: 0;		
	border: none;
}
#header #quick-search input {
	float: left;
	margin: 0; padding: 5px;
	border: none;
	background: transparent;
	color: #4a413c;	
}
#header #quick-search .tbox {
	margin: 6px 0 0 5px; 
	width: 170px;	
	display: inline;		
}
#header #search #quick-search .btn{
	width: 25px; height: 25px;		
}
#header #quick-search label {
	display: none;
}

Lets add a click event method to the button and the necessary code for redirecting the user to our search page.

<asp:ImageButton runat="server" OnClick="btnQuickSearch_Click" CssClass="btn" ID="btnQuickSearch" CausesValidation="false" ToolTip="Search" ImageUrl="/Templates/Jungle/images/search.png" />
using System;
using System.Web;
using EPiCode.Extensions;
using EPiServer.Core;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Units
{
    public partial class QuickSearch : UserControlBase<BasePageData>
    {
        protected void btnQuickSearch_Click(object sender, EventArgs e)
        {
            var startPage = PageReference.StartPage.GetPage() as StartPage;
 
            if (startPage == null || PageReference.IsNullOrEmpty(startPage.SearchPage))
            {
                return;
            }
 
            PageData searchPage = startPage.SearchPage.GetPage();
 
            if (searchPage != null)
            {
                Response.Redirect(HttpUtility.UrlPathEncode(string.Format("{0}&q={1}", searchPage.LinkURL, txtQuickSearch.Text)));
            }
        }
    }
}

As you can see we’re getting the search page from the dynamic property SearchPage.

I’ve also added suggestion functionality to the quick search field. See my post Extending search field with suggestion box, for more information. All the code is also available on EPiCode.

Quick Search Suggestion in action

Remember to remove the Visible=”false” property from QuickSearch in PageHeader.ascx.

Search page

We can now start creating the search page. Create the Search page type by creating a new class with the name SearchPage, and the proper attribute declaration.

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

For this page type we only need three properties. Heading, MainBody, and SearchRoot. Since we already have the code for Heading and MainBody in StandardPage, we’re going to refactor and create a super class that StandardPage and SearchPage can derive from.

using EPiServer.Core;
using EPiServer.Editor;
using EPiServer.SpecializedProperties;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    public abstract class EditorialPage : 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;
        }
    }
}

We can now update StandardPage to derive from EditorialPage instead of BasePageData, and delete the Heading and MainBody property.

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 : EditorialPage
    {
        [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;
        }
    }
}

Lets also update SearchPage to derive from EditorialPage and add the SearchRoot property.

using EPiServer.Core;
using PageTypeBuilder;
 
namespace Jungle.Templates.Jungle.PageTypes
{
    [PageType(
        Name = "[Jungle] Search page ",
        Filename = "/Templates/Jungle/Pages/Search.aspx",
        SortOrder = 1030)]
    public class SearchPage : EditorialPage
    {
        [PageTypeProperty(
            EditCaption = "Search root page",
            Type = typeof(PropertyPageReference))]
        public virtual PageReference SearchRoot
        {
            get;
            set;
        }
    }
}

Go back to edit mode and create the search page, just set the SearchRoot to our start page. Then go to the start page and point the SearchPage dynamic property to the search page (remember to check the checkbox that tells EPiServer to inherit this to sub pages). After you’ve done that make sure that quick search redirects to the correct page.

Search.aspx

We can now create Search.aspx.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Search.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.Search" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Pages
{
    public partial class Search : TemplatePage<SearchPage>
    {
    }
}

Standard stuff :).

The code for our search page consists of a TextBox, Button, ListView for the result, some text if there are no results, and a SearchDataSource control for searching.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Search.aspx.cs" Inherits="Jungle.Templates.Jungle.Pages.Search" MasterPageFile="~/Templates/Jungle/MasterPages/Site.Master" %>
<%@ Import Namespace="EPiServer.Core"%>
<asp:Content runat="server" ContentPlaceHolderID="MainContentRegion">
    <h2><%= CurrentPage.Heading() %></h2>
    <EPiServer:Property runat="server" PropertyName="MainBody" DisplayMissingMessage="false" EnableViewState="false" />
 
    <div id="SearchArea">
        <asp:Label runat="server" AssociatedControlID="txtSearch" CssClass="accessibility">Search:</asp:Label>
        <asp:TextBox ID="txtSearch" text="Search..." runat="server" />
        <asp:Button ID="btnSearch" CssClass="button" runat="server" OnClick="btnSearch_Click" Text="Search" /><br />
    </div>
 
        <div id="ResultArea">
 
            <asp:ListView ID="SearchResult" DataSourceID="SearchDataSource" runat="server" Visible="false" ItemPlaceholderID="itemPlaceHolder">
                <ItemTemplate>
                    <li>
                        <h3><EPiServer:Property runat="server" PropertyName="PageLink" /></h3>
                        <p>
                             <%# ((PageData)Container.DataItem).PreviewText(150)%>
                        </p>
                    </li>
                </ItemTemplate>
                <LayoutTemplate>
                    <h2>Search result</h2>
                    <ol>
                        <asp:PlaceHolder runat="server" ID="itemPlaceHolder" />
                    </ol>
                </LayoutTemplate>
                <EmptyDataTemplate>
                    <p>No matching results were found</p>
                </EmptyDataTemplate>
            </asp:ListView>
        </div>
 
        <EPiServer:SearchDataSource ID="SearchDataSource" runat="server" EnableVisibleInMenu="false">
        <SelectParameters>
            <EPiServer:PropertyParameter Name="PageLink" PropertyName="SearchRoot" />
            <asp:controlparameter name="SearchQuery" controlid="txtSearch" propertyname="Text"/>
        </SelectParameters>
    </EPiServer:SearchDataSource>
</asp:Content>
using System;
using Jungle.Templates.Jungle.PageTypes;
using PageTypeBuilder.UI;
 
namespace Jungle.Templates.Jungle.Pages
{
    public partial class Search : TemplatePage<SearchPage>
    {
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            Form.DefaultButton = btnSearch.UniqueID;
 
            if (!IsPostBack)
            {
                string query = Request.QueryString["q"];
                if (!String.IsNullOrEmpty(query))
                {
                    txtSearch.Text = query;
                    SearchResult.Visible = true;
                }
            }
        }
 
        protected void btnSearch_Click(object sender, EventArgs e)
        {
            SearchResult.Visible = true;
            SearchResult.DataBind();
        }
    }
}

A very simple search page that looks something like this when used.

Search result

The next post: Part 6: Creating the XForm page.

Other resources

Related Posts: