Tuesday, May 22, 2007

Creating a Custom Control Feature

Heya folks,

It's time for another Feature! Woo HOO!! This a definite must know for WSS 3.0 and MOSS 2007. Ok, so this feature will deploy a custom search box control that can be called in your own Master Page or Page Layout. Let's walk through the steps needed to create this Cool, reusable feature.

1.)Let's Create a Custom Control! Create a file named "CustomSearch.ascx", this file will be just a modified version of the current WSS 3.0/MOSS Search.

2.)Copy the following code into your "CustomSearch.ascx" control. This code only modifies the template of the "out-of-the-box" search (mainly just a little space between the search and drop-down) and still maintains all the functionality of the original.


< %@ Control Language="C#" Inherits="Microsoft.SharePoint.WebControls.SearchArea,Microsoft.SharePoint,Version=12.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" compilationMode="Always" % >
< %@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" % >
< %@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" % >
< %@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" % >
< %@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" % >
< %@ Import Namespace="Microsoft.SharePoint" % >
< %
string strScopeWeb = null;
string strScopeList = null;
string strWebSelected = null;
SPWeb web = SPControl.GetContextWeb(Context);
string strEncodedUrl
= SPHttpUtility.EcmaScriptStringLiteralEncode(
SPHttpUtility.UrlPathEncode(web.Url + "/_layouts/searchresults.aspx", false, false)
);
strEncodedUrl = "'" + strEncodedUrl + "'";
strScopeWeb = "'" + SPHttpUtility.HtmlEncode( web.Url ) + "'";
SPList list = SPContext.Current.List;
if ( list != null &&
((list.BaseTemplate != SPListTemplateType.DocumentLibrary && list.BaseTemplate != SPListTemplateType.WebPageLibrary) ||
(SPContext.Current.ListItem == null) ||
(SPContext.Current.ListItem.ParentList == null) ||
(SPContext.Current.ListItem.ParentList != list))
)
{
strScopeList = list.ID.ToString();
}
else
{
strWebSelected = "SELECTED";
}
% >
< table border="0" cellpadding="0" cellspacing="0" class='ms-searchform' >< tr >
< td >

< input name="SearchString" type="text" value="Search..." id="idSearchString" title="Enter Search Words" class="ms-searchbox" onfocus="if (document.getElementById('sb_state').value =='0') {this.value=''; document.getElementById('sb_state').value=1;}" onblur="if (this.value =='') {this.value='Search...';document.getElementById('sb_state').value = '0'} else {document.getElementById('sb_state').value='1';}" style="width:120px" maxlength="255" ACCESSKEY="S" onKeyDown="return SearchKeyDown(event, < %=strEncodedUrl% >);" title=< %SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SearchTextToolTip),Response.Output);% >/ >
< input type="hidden" name="sb_state" id="sb_state" value="0" / >
< /td >
< td style="width:4px;" > < /td >
< td >
< select id='idSearchScope' name='SearchScope' class='ms-searchbox' title=< %SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SearchScopeToolTip),Response.Output);% > >
< option value=< %=strScopeWeb% > < %=strWebSelected% > > < SharePoint:EncodedLiteral runat="server" text="< %$Resources:wss,search_Scope_Site% >" EncodeMethod='HtmlEncode' Id='idSearchScopeSite'/ > < /OPTION >
< %
if (strScopeList != null)
{
% >
< option value=< %=strScopeList% > SELECTED > < SharePoint:EncodedLiteral runat="server" text="< %$Resources:wss,search_Scope_List% >" EncodeMethod='HtmlEncode' Id='idSearchScopeList'/ > < /OPTION >
< %
}
% >
< /select >
< /td >
< td >
< div class="ms-searchimage" >< a target='_self' href='javascript:' onClick="javascript:SubmitSearchRedirect(< %=strEncodedUrl% >);javascript:return false;" title=< %SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SearchImageToolTip),Response.Output);% > ID=onetIDGoSearch >< img border='0' src="/_layouts/images/gosearch.gif" alt=< %SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(SearchImageToolTip),Response.Output);% >/ >< /a >< /div >
< /td >
< /tr >< /table >



3.)Create a feature folder named CustomSearch, this folder will host our feature.

4.)Next, let's create a "Feature.xml" file within our feature folder. This file will tell SharePoint that we have a feature we'd like it to acknowledge. Place the following code within your Feature.xml file:


< ?xml version="1.0" encoding="utf-8" ? >

< Feature Id="74CC942C-C6F9-48a7-A5CC-1856EE7BABEB"

Title="CustomSearch"

Scope="Farm"

Description="Our Custom Search"

Version="1.0.0.0"

xmlns="http://schemas.microsoft.com/sharepoint/" >

< ElementManifests >

< ElementManifest Location="CustomSearchArea.xml" / >

< /ElementManifests >

< /Feature >


You'll notice that we have all of the needed attributes for our feature.xml element (ID [guid], Title, description, etc..), this is fairly straight forward. The interesting piece of this xml file comes in the "ElementManifest" element, this element has a reference to a file not yet created called "CustomSearchArea.xml". "CustomSearchArea.xml" will contain specific meta-data for our new Custom Search control. Let's create that file.

5.)Create our "CustomSearchArea.xml" file within our feature folder and copy the following xml inside:


< ?xml version="1.0" encoding="utf-8"? >
< Elements xmlns="http://schemas.microsoft.com/sharepoint/" >
< Control
Id="CustomSearchBox"
Sequence="100"
ControlSrc="~/_controltemplates/Customsearcharea.ascx" >
< /Control >
< /Elements >



This is the only "quasi" tricky part in this walkthrough. As we've seen with our previous Feature, the "Elements" element contains the actual definition for the item we're provisioning or creating. The "Control" element contains the definition of our new Custom Control. This element has ID, Sequence, and ControlSrc attributes which directly reference the control we're creating. In our control we define an ID of "CustomSearchBox", a custom sequence of "100", and in our ControlSrc we directly reference the "CustomSearchArea.ascx" file (In our case this file will live in the "ControlTemplates" folder of our 12 hive). These attributes gives SharePoint all the info it needs to declare a new Custom control.

6.)Now, let's test our feature (I'll do a Solution Pack for my next post, but for now we'll have to do the work around). Copy your the "CustomSearch" folder into the 12/Templates/Features folder. Now copy the "CustomSearch.ascx" file into the 12/Templates/ControlTemplates. Once everything is copied run the following commands:

stsadm -o installfeature -name CustomSearch -force

There you have it! If you open Central Administration => Application Management => Farm Features, you'll notice that our new custom search control is there waiting to be activated!

Well another bit of code well done. I know this was a whirl wind so if you have any questions just drop me a comment.

~:)

13 comments:

Christopher said...

I just thought I'd mention how sticky customizing and replacing the search delegate control can be for those interested in doing this. Having 1st hand knowledge of this myself, using this can lead to altering and limiting the scopes in which SharePoint wants to search.

Anonymous said...

It is actually Central Administration -> Operations -> Manage Farm Features

Anonymous said...

When you are referred to CustomSearchArea.ascx at the end, do you mean CustomSearch.ascx instead?
I assumed that is the case and just used CustomSearchArea.ascx as the file name and copied in under the Control Templates. But when i run the command, I get an erroe saying :'stsadm' is not a external or internal command.
I am new to ASP.Net and to sharepoint.

Eric Stallworth said...

stsadm.exe lives in the C:\Program Files\Common Files\Microsoft Shared\web server extensions\BIN

Eric Stallworth said...

stsadm.exe lives in the C:\Program Files\Common Files\Microsoft Shared\web server extensions\BIN

Anonymous said...

Does it matter what the feature ID is? If it has to be unique then how do I make sure that this ID is not being used in built-in features?

Thank you

Eric Stallworth said...

Well You can generate a new GUID using Visual Studio GUID Generator. It's very very unlikely that 2 Guids would ever be the seam. 32 characters that are alpha numeric. ~:)

Jason said...

This is great and helped me to create a solution for my site however I have been unable to get the scopes to work (site scopes). From looking around that is because in MOSS SearchBoxEX class is being used and not this control template. Anyone have an idea about how to get the scopes to work using this method on MOSS 2007?

Anonymous said...

I am having the same issue as Jason. Has anyone figured out how to add scopes to the custom search control?

Jason said...

I ended up building my own control with jQuery. I decided to just use html and js so the scopes are just a select/option list that we can manually update if we add additional scopes. We have one central search and the new control overrides the search box everywhere in our farm. its pretty simple to add a new scope and upgrade the control solution. This hasn't happened yet since we deployed last year.

Aidan said...

Thanks Jason. How did you pass your scopes to the javascript? SubmitSearchRedirect method that is used in the example does not seem to have an option for the &s= parameter. Did you just write your own javascript method?

Jason said...

I wrote my own. I just copied the code into a google doc (I took out my company specific references):

http://docs.google.com/Doc?id=dhm8z4cf_3dm35jggj

Let me know if you have any questions.

Anonymous said...

I am completely impressed with the article I have just read. I wish the author of www.theartofsharepoint.com can continue to provide so much useful information and unforgettable experience to www.theartofsharepoint.com readers. There is not much to tell except the following universal truth: No man can ever know both what a woman is thinking, and how she will act on this, at the same time. (apologies to Heisenberg) I will be back.