|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThe built-in Windows SharePoint Services search results page (SearchResults.aspx) is rather limited in the document metadata that it displays in the result list. Furthermore, the format of the list is not what every client wants. My client wanted lots of document metadata displayed in a "table", and only wanted results from a single document library in the site. So, I created a custom search page to implement this functionality. To display the results, I used a This example shows how to create a custom search results page that will display any metadata available for a document. It is totally flexible in that it gets the metadata fields to display from a client-configured document library view. This means that if the client decides s/he wants more (or fewer) categories to display, or wants to change their order, the results page will automatically adjust when the document library view is changed. This solution uses a simple Content Editor web part with some client-side HTML and JavaScript to invoke the search. This leaves the original search box and search results page for normal site-wide searches (which includes multiple libraries and lists). Summary of Features:
Sample results page:
BackgroundIn order for you to understand and apply the contents of this article, you need to know your way around SharePoint - both in custom development, and in standard configuration. In particular, you need to know how to create a custom view for a document library, and how to add and configure a content editor web part. These are standard configuration processes not requiring any hard-core development. This article also assumes you know a little bit about the SharePoint object model. Using the CodeThere are three main steps to implementing this solution: creating the custom search aspx page, configuring a "view" on a SharePoint document library, and adding a Content Editor web part to a SharePoint page. We will cover them in decreasing order of complexity. The ASPX PageTo begin, make a copy of the original search results page. In a default installation, this is found here: C:\Program Files Common Files\Microsoft Shared\web server extensions\60\TEMPLATE\LAYOUTS\1033. You can give it any name you want, such as CustomSearchResults.aspx. By copying the original, you get all the "chrome" that should be on a standard SharePoint page, to ensure your page looks like part of the site. Be aware that a re-install of SharePoint will likely overwrite this directory, wiping out your file, so be sure to save a backup somewhere! All of our code has to go in the aspx page itself (no code-behind), since we can't rebuild SharePoint as a web app. The page will read two values from the query string (passed from our custom search form on another page). These values are:
Once you've created a copy of the page, do the following:
Let's go into more details on step 4, shall we? There's actually a lot more happening in that server-side code block! There are four server-side methods: Page_Init MethodThis method is used to dynamically create the One thing to note is that the fields collection returns the internal field name - not the display name. The internal name is perfect for manipulating the Another important item of note regards the sorting capability of the Here's the void Page_Init(Object Sender, EventArgs e)
{
//Get reference to site from querystring
string url = Request.QueryString["Target"].ToString();
SPSite siteCollection = new SPSite(url);
SPWeb CRsite = siteCollection.OpenWeb();
//get reference to target document library
SPListCollection AllLists = CRsite.Lists;
AllLists.IncludeRootFolder = true;
//you could pass this in querystring
SPList ContRep = AllLists["DocLibName"];
//get field objects from Content Repository
// - needed to obtain display name of fields
SPFieldCollection ListFields = ContRep.Fields;
//get field names used in search results view
//(these are "internal" names, not display names)
SPView SRView = ContRep.Views["Search Results"];
SPViewFieldCollection SRViewFields = SRView.ViewFields;
System.Collections.Specialized.StringCollection
InternalViewFldNames = SRViewFields.ToStringCollection();
//using internal names, build the datagrid columns, in field order
BoundColumn C = null;
string str="";
for (int i=0; i< InternalViewFldNames.Count; i++)
{
C = new BoundColumn();
if (i>0) //no header for doc icon
{
C.HeaderText = ListFields.GetFieldByInternalName(
InternalViewFldNames[i]).Title;
//this is the DISPLAY name
//change "name" column to be "file name"
if (C.HeaderText == "Name")
C.HeaderText = "File Name";
if (C.HeaderText == "Title")
C.SortExpression = "SortByTitle";
}
C.DataField = InternalViewFldNames[i];
C.ItemStyle.VerticalAlign = VerticalAlign.Top;
DataGrid1.Columns.Add(C);
}//end for loop
//add hidden column for sorting by title (NOT URL of title!)
C = new BoundColumn();
C.HeaderText = "SortByTitle";
C.SortExpression = "SortByTitle";
C.DataField = "SortByTitle";
C.Visible = false;
DataGrid1.Columns.Add(C);
}//end page_init
BindData MethodThe
It takes one parameter: Let's look at this method in more detail: The search is performed by invoking the //get search string from querystring
string SearchString = Request.QueryString["CRSearchString"].ToString();
SearchString = Server.UrlDecode(SearchString);
lblSearchString.Text = SearchString;
//Invoke document search with string passed in
SPSearchResultCollection Results =
CRsite.SearchDocuments(SearchString);
We loop through this collection, saving the document URL (which contains the document ID, to be used later) and the URL to the document icon (to be displayed in the grid). Because the search will have returned items from throughout the site, and we only wanted to search documents in a particular document library, we only keep items that have the specified document library address in their URL: //place URL of each doc in arraylist for later processing
string itemURL = "";
foreach(SPSearchResult item in Results)
{
itemURL = item.Url.ToString();
if (itemURL.IndexOf("/sitename/documentlibraryname/") > 0)
//only keep docs in target library
{
//increment actual result count
ActualResultCount++;
//save document URL
DocURLs.Add(itemURL);
//save doc icon URL
IconURLs.Add(item.IconUrl.ToString());
}
}//end foreach item in search results collection
Next, we obtain the fields specified in the search results view for the specified document library, and loop through them to build our table to hold the results: //get field objects from list (which contain
//the display names of the fields)
SPFieldCollection ListFields = ContRep.Fields;
//get field names used in search results view
//(these are "internal" names, not display names)
SPView SRView = ContRep.Views["Search Results"];
//"Search Results" is the name of the doc lib view
SPViewFieldCollection SRViewFields = SRView.ViewFields;
System.Collections.Specialized.StringCollection
InternalViewFldNames = SRViewFields.ToStringCollection();
//string s="";
//process each view field, building results table columns
for (int j=0; j< InternalViewFldNames.Count; j++)
{
ResultsTable.Columns.Add(new DataColumn(
InternalViewFldNames[j], typeof(string)));
//s += InternalViewFldNames[j] + " ";
}
//add the hidden column
ResultsTable.Columns.Add(new DataColumn("SortByTitle",
typeof(string)));
We then loop through the document URL array list to obtain the document ID, use that to get the actual document object, extract the needed metadata, build a results row, and add it to a results table: //for each doc url in array, get document object
//and build results row
for (int k=0; k< DocURLs.Count; k++)
{
//using URL, get id of item to retrieve item object
//URL is in this format: http://server/sitecollection/currentsite/
documentlibraryname/Forms/DispForm.aspx?ID=nn
strURL = DocURLs[k].ToString();
//extract only the digits after "?ID=" or "&ID="
Regex exp1 = new Regex(@"[\?|&]ID=([0-9]+)");
if (!exp1.IsMatch(strURL))
//ignore any file w/o and ID, since it's not
//a list item (some web pages in forms library might match)
{
continue;
}
ID = exp1.Matches(strURL)[0].Groups[1].Value; //save the ID
//get the item object using the ID just obtained
ThisItem = ContRep.GetItemById(Convert.ToInt32(ID));
//create new row for item
ResultsRow = ResultsTable.NewRow();
//for each view field name, fill results row with item metadata
for (int j=0; j< InternalViewFldNames.Count; j++)
{
//use the internal field name to obtain the title
ResultsRow[InternalViewFldNames[j]] =
ThisItem[ListFields.GetFieldByInternalName(
InternalViewFldNames[j]).Title];
strField = ResultsRow[InternalViewFldNames[j]].ToString();
//some fields may have "x;#" at the beginning - strip
//it off as well as any others
if (strField.IndexOf("#") >=0)
{
//get's the first occurrence
strField = strField.Substring(strField.IndexOf("#")+1);
//get rid of any other #'s, leaving the ";"
strField = strField.Replace(";#", ";");
ResultsRow[InternalViewFldNames[j]] = strField;
}
}//end foreach field name
//customize "special columns":
//make icon row display an image
ResultsRow["DocIcon"] = "< a href='" + strURL +
"'>< img src='" +
IconURLs[k].ToString() +
"' border=0 ></a>";
//make doc name link to doc property page
ResultsRow["Title"] = "< a href='" + strURL + "'>" +
ThisItem["Title"] + "</a>";
//set value of sorting row to the actual document title
ResultsRow["SortByTitle"] = ThisItem["Title"].ToString();
//add row to table
ResultsTable.Rows.Add(ResultsRow);
}//end for each docURL
There are a couple of things to note in the above code. First, multi-valued fields have their values in a string, separated by "x;". Even if there is only one value, it will begin with those characters. So we strip those out, but leave a semicolon in between values. Secondly, there are three "special" columns that require some extra tweaking:
The last bit of code in this method places the Page_Load MethodThis method is very simple. All it does is call the DataGrid1_Sort MethodThe last method in the server-side code block is the method to apply the sort. It simply calls void DataGrid1_Sort(object source,
System.Web.UI.WebControls.DataGridSortCommandEventArgs e)
{
BindData(e.SortExpression);
}
Client-side Custom CodeThe only other custom code on the page is the client-side JavaScript to allow the user to re-search. This is the same code you place in the content editor web part on the page to start the search process. It's very straightforward: function DoCRSearch()
{
//get value of textbox
//CRSearchString is the name of the textbox
var obj = document.getElementById("CRSearchString");
var strSearch = obj.value;
//make sure we don't have empty string
if (strSearch == "")
alert("Please enter a search term.");
else
{
//URL encode search string
strSearch = escape(strSearch);
//build target URL for site
var loc = document.location.href;
var baseURL = loc.substring(0,loc.lastIndexOf("/"));
var strTarget = baseURL + "/default.aspx";
//redirect to search page, adding on target URL
//and search string as querystring parameters
document.location.href = baseURL +
"/_layouts/1033/searchresultsCR.aspx?CRSearchString=" +
strSearch + "&Target=" + strTarget;
}//end else
}//end function
Note that if you place this code on a standard SharePoint page, don't name the textbox "SearchString". This is the name of the textbox used by the built-in SharePoint search box, and the function will grab it, instead! To use this code, insert it in a client-side script block. Then, place an HTML textbox and button on your page, and wire up the button to call the above method. Do not place these in a client-side Because this custom page has all of the HTML for the "chrome" of your site, your custom code (the HTML, the Document Library ViewCreating a view on a SharePoint document library is very simple. From the document library page, click the "Modify Settings and Columns" link in the left-hand action menu. On this page, you can specify the metadata for the documents, and at the bottom of the page is the link to allow you to create a new view or modify an existing view. You can select the columns to be displayed, and also specify their order. Create a view called "Search Results" (or whatever you want - just make sure the Content Editor Web PartTo invoke the search from another page on the site, simply add a content editor web part, placing the JavaScript function and the HTML textbox and button in it. You can add any other content/formatting as you choose. To add a content editor web part to a page, click the "Modify this page" link, then choose "Add Web Parts | Browse...", and drag the content editor web part onto your page. Use the source editor to add your HTML and JavaScript. Points of InterestWhen I first started this project, I thought it would be fairly simple - just take the object returned by the search tool and access the other metadata fields. Then I discovered that the object returned by the SharePoint search engine does not include all the metadata for the documents. Hence the need for using the ID obtained from the search result object to obtain the actual document object, which does contain the metadata. Adding somewhat to the complexity, was using the sometimes awkward SharePoint object model. For example, dealing with both the internal name and the display name of the metadata fields led to code like this: Another "gotcha" was the problem of sorting the grid by title. During testing, sorting on all the other columns was fine, but the Title column was not sorting properly. Since the sort is a method of the History
|
||||||||||||||||||||||