.NET Version Note

If you are using .NET 5+ or .NET 2, you must also add one these lines in the examples below

Javascript

keyotiSearch.setServiceType('core'); //for .NET 5+

keyotiSearch.setServiceType('asmx'); //for .NET 2

Single Page Setup

Assuming that the SearchUnit scripts are located in a sub folder named Keyoti_SearchEngine_Web_Common, and the index is in a sub folder named Keyoti_Search_Index. The following page will create a search box control inside the DIV with id sew_searchBoxControl, and a search result viewer inside the DIV with id sew_searchResultControl.

Javascript / HTML
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
        <link href="Keyoti_SearchEngine_Web_Common/SearchUnit.css" rel="stylesheet" />
        <script src="Keyoti_SearchEngine_Web_Common/SearchUnit.js"></script>
    </head>
    <body>
        <div id="sew_searchBoxControl"></div>
        <div id="sew_searchResultControl"></div>
    </body>
</html>

Note: If you get this error "Request format is unrecognized for URL unexpectedly ending in ..." then you may need to add the HttpPost protocol to the webservices section of web.config, eg.

<configuration>
    <system.web>
    <webServices>
        <protocols>
            <add name="HttpGet"/>
            <add name="HttpPost"/>
        </protocols>
    </webServices>
    </system.web>
</configuration>

Example output after a query has been entered.

If the script folder is not the default Keyoti_SearchEngine_Web_Common then it is necessary to point to the folder;

Javascript / HTML
    ...
        <link href="/myscriptfolder/SearchUnit.css" rel="stylesheet" />
        <script src="/myscriptfolder/SearchUnit.js"></script>
        <script type="text/javascript">
            keyotiSearch.commonFolderUrl = "/myscriptfolder";
        </script>
    ...

If the index directory is not the default Keyoti_Search_Index then it is necessary to point to the folder (use the tilda ~ notation to point to the application root, or use an absolute file path);

Javascript / HTML
    ...
        <script type="text/javascript">
            keyotiSearch.indexDirectory = "~/myindexdirectory";
        </script>
    ...

Two Page Setup

Building on the Single Page Setup example above, it is possible to initiate the search from another page.

Javascript / HTML : index.html (page with the search box)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
        <link href="Keyoti_SearchEngine_Web_Common/SearchUnit.css" rel="stylesheet" />
        <script src="Keyoti_SearchEngine_Web_Common/SearchUnit.js"></script>
        <script type="text/javascript">
            if (location.href.toLowerCase().indexOf('/search.html') == -1) 
                    keyotiSearchBox.resultURL = "search.html";
        </script>
    </head>
    <body>
        <div id="sew_searchBoxControl"></div>
    </body>
</html>

Be sure to change the IF condition and the resultURL to the correct URL of the result page, the IF will stop the page endlessly redirecting in the event that this code is present on the result page (or header/master page).

Javascript / HTML : search.html (page with the search results)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
        <link href="Keyoti_SearchEngine_Web_Common/SearchUnit.css" rel="stylesheet" />
        <script src="Keyoti_SearchEngine_Web_Common/SearchUnit.js"></script>
    </head>
    <body>
        <div id="sew_searchBoxControl"></div>
        <div id="sew_searchResultControl"></div>
    </body>
</html>

If you are using .NET 2, you must also call

Javascript
    keyotiSearch.useWCFService=false;

Templating The Controls

SearchBox

The sew_searchBoxControl can be laid out in anyway, provided it has at least an <input type="text" id="sew_searchBox" /> element (extra attributes such as class and style can be added too) and an element with id sew_searchButton.

Link for a button

HTML
    <div id="sew_searchBoxControl">
        <input type="text" id="sew_searchBox" style="border: 2px solid black; font-size:20px;"/>
        <a href="#stayhere" onclick="keyotiSearchBox.doSearch()" id="sew_searchButton">Search</a>
    </div>

Image for a button

HTML
    <div id="sew_searchBoxControl">
        <input type="text" id="sew_searchBox" />
        <a href="#stayhere" onclick="keyotiSearchBox.doSearch()" id="sew_searchButton"><img src="...." /></a>
    </div>

Image roll-over effects can be achieved using conventional techniques such as CSS sprites or Javascript mouseovers.

SearchResult

The sew_searchResultControl has a template mechanism to define it's layout and elements (such as custom data). There are 3 component DIVs to the sew_searchResultControl, which may be optionally specified; div class='sew_ajax_loader_backer' (which holds the loading animation, defined in CSS), div class='sew_ajax_error' (which holds any error messages) and div id='sew_resultView' (which displays search results).

sew_resultView contains 3 DIV components, which are the header (id='sew_resultHeader'), result list (id='sew_resultList') and footer (id='sew_resultFooter').

HTML
    <div id="sew_searchResultControl">
        <div class="sew_ajax_loader_backer"><div class="sew_ajax_loader"></div></div> 
        <div class="sew_ajax_error">
            <span class='sew_errorTitle'>Error</span>
            <p class='sew_errorBody'></p>
            <div class="sew_ajax_error_footer">
                <input type="button" value="OK" id="sew_errorOKButton" />
            </div>
        </div>
        <div id="sew_resultView">
            <div id="sew_resultHeader">
            </div>
            <div id="sew_resultList">
            </div>
            <div id="sew_resultFooter"></div>
        </div>
    </div>

Elements can be arranged as desired and templates defined by the developer can control the layout and content of the header, result list and footer.

Header template

For example;

HTML
    ...
    <div id="sew_resultHeader">                   
        <div id="sew_headerTEMPLATE" class="sew_header">
            Showing result page <b>${PageNumber}</b>. 
            There are <b>${NumberOfResults}</b> results for <b>&#8220;${Query}&#8221;</b>. 
            <span id="sew_ignoredWords">The following common words were ignored: ${IgnoredWords}</span>
            <div id="sew_didYouMean">Did you mean: <a href="#stayhere" 
                    onclick="keyotiSearch.search('${SuggestedSearchExpressionEscaped}', 1)">${SuggestedSearchExpression}</a></div>
        </div>               
    </div>
    ...

The dynamic elements are: ${PageNumber} (the result page being shown), ${NumberOfResults} (the number of results in total), ${Query} (the search query that the results are for) and ${IgnoredWords} (list of any common words that were ignored, the stop words). It is also possible to place other controls, such as the sort control inside the header, this is discussed in the custom data filters section.

Note that it is important to wrap the 'ignored words' section with an element with id sew_ignoredWords, because this element is automatically made invisible if there are no ignored words.

Note that it is important to wrap the 'did you mean' section with an element with id sew_didYouMean, because this element is automatically made invisible if there are no misspelled words.

Footer template

For example;

HTML
    ...
    <div id="sew_resultFooter">                   
        <div id="sew_footerTEMPLATE" class="sew_footer">
            <span id="previousPageLink">${PreviousPageLink}</span> 
            <span id="pageLinksBlock">${PageLinksBlock}</span>
            <span id="nextPageLink">${NextPageLink}</span>
        </div>               
    </div>
    ...

The dynamic elements are: ${PreviousPageLink} (link text to the previous page), ${PageLinksBlock} (links to each page number), and ${NextPageLink} (link text to the next page).

Result list template

For example;

HTML
    ...
    <div id="sew_resultList">
                    
        <div id="sew_resultItemTEMPLATE" class="sew_resultItem">
                <span class="sew_resultItemLink"><a href="${UriStringWithKeywords}">${ResultNumber} ${Title}</a></span>
                <span class="sew_resultItemSummary">${Summary}</span>
                <span class="sew_previewResultWrapper"><img alt="Click to preview the document text" src="/Keyoti_SearchEngine_Web_Common/ResultPreview_Expander_Closed.png"
                    onclick="keyotiSearchResultPreviewer.toggleResultPreview(this,
                    '${UriStringAsStored}',
                    '/Keyoti_SearchEngine_Web_Common/ResultPreview_Expander_Closed.png',
                    '/Keyoti_SearchEngine_Web_Common/ResultPreview_Expander_Opened.png')"/>
                <span class="sew_previewResultContent">Loading document...</span></span>
                <div style="clear:both; height:1px;"></div>
                <span class="sew_resultItemURL">${UriString}</span>
				<span class="sew_keywordHitMap">${KeywordHitMap}</span>
                <span class="sew_location">${Location}</span>
                <span class="sew_location">${Content}</span>
                        
                <!--Example Custom data elements-->
                <img data-src="${CustomDataDictionary.img}" />
                <span class="${CustomDataDictionary.dateDisplayClass}">Publish date: ${CustomDataDictionary.date}</span>                                                                                                                                                                                                           
            </div>                        
                    
    </div>
    ...

There are no requirements for layout, element types or data elements to show - the above is just an example that uses what is available.

The dynamic elements are: ${UriString} (the URL for the result), ${ResultNumber} (the result number), ${Title} (the document/page title), ${Summary} (the result summary, this can be set to be dynamic or statically generated, see configuration), ${UriStringAsStored} (the document URI as it is contained in the index, this can be different to ${UriString}, typically for database results), ${KeywordHitMap} (a breakdown of number of hits within each document for each keyword), ${Location} (the document's location category), ${Content} (the document's content categories) and ${CustomDataDictionary.*} (custom data fields).

Custom data dictionary fields
Please first see How to add CustomData to a document

If the document contains custom data, stored in URL encoded GET parameter style (eg. fieldName1=data1&fieldName2=data2) then that data is made available to the template through the CustomDataDictionary object. In the above example, the 'img' and 'date' fields from the document's custom data are displayed.

Custom Data Dictionary formatting:

Note that the img tag set "data-src" instead of "src", this is only because if the "src" attribute is set to ${CustomDataDictionary.img}, the browser will attempt (and fail) to download the image from the URL http://.../${CustomDataDictionary.img}. By using data-src this is avoided since the "src" attribute is only set when the template is used for result generation.

Hiding elements when a document doesn't contain a custom data field

In order to hide the custom data field and its surrounding text, the dynamically generated CSS class ${CustomDataDictionary.dateDisplayClass} is used. Each field has a dynamically generated class ${CustomDataDictionary.fieldNameDisplayClass}. When there is no text in the custom data field, the class name will be fieldNameNoContent, and when there is content it will be fieldNameContent. For example;

HTML
     ...
    <span class="${CustomDataDictionary.authorDisplayClass}">Author: ${CustomDataDictionary.author}</span>
    ...

If the 'author' custom data field contains no data, the generated HTML will be;

HTML
     ...
    <span class="authorNoContent">Author: </span>                                                                     
    ...

Therefore adding a style rule to hide the span will make the "Author" text invisible if there is no author information.

CSS
    .authorNoContent{
        display:none;
    }
Formatting or changing custom data field text

The text output from ${CustomDataDictionary.fieldName} is directly from the custom data field in the index, however it can be modified with a Javascript function at search time. For example, specify a function for keyotiTemplateUtility.formatCustomData;

Javascript
    keyotiTemplateUtility.formatCustomData = function (customDataDictionaryItemName, value) {
        if (customDataDictionaryItemName == 'publishDate' && value != null && value.length == 10) {
            //format date to user's locale
            var date = new Date(value.substring(0, 4), value.substring(5, 7) - 1, value.substring(8, 10));
            return date.toLocaleDateString();
        }

        //default provide as-is.
        return value;
    };
    

This code (which should be called after the Keyoti scripts have been imported) will convert the date format for the 'publishDate' field from yyyy-mm-dd to the user's locale format (eg. mm/dd/yyyy in the US).

Processing results before they are shown

To process the search results in Javascript (as an alternative to processing them server side with the Plugin API) specify the keyotiSearch.onResultsObtained function on the search results page. This function will be called when the search results are ready to be presented. It is possible to manipulate not just the existing fields but also add new fields.

Eg. adding a FileType field based on the result URL - the FileType can then be accessed from the result template (see below)

Javascript
        var fileExtensions = ["pdf", "pptx", "ppt", "docx", "doc", "htm"];
        keyotiSearch.onResultsObtained = function (resultObject) {
            for (var i = 0; i < resultObject.Results.length; i++){
                for (var j = 0; j < fileExtensions.length; j++) {
                    if (resultObject.Results[i].UriString.indexOf(fileExtensions[j]) > -1) {
                        resultObject.Results[i].FileType = fileExtensions[j].toUpperCase();
                        break;
                    }
                }
                
            }
        }

    

The FileType field did not exist before this function was called, however already present fields are:

Exception, IgnoredWords, LiveDataSamples, NumberOfResults, QueryKeywords, Results array. Results array items contain Content, CustomData, CustomDataDictionary, KeywordHitMap, Location, SecurityGroups, Summary, Title, UriString, UriStringAsStored, Weight (modifying weight will not change result ordering which is already determined).

The fields are then available to the result item template, eg. to use the FileType field from above

HTML
            
<div id="sew_searchResultControl">
    <div class="sew_ajax_loader_backer"><div class="sew_ajax_loader"></div></div>
    <div class="sew_ajax_error">
    <span class='sew_errorTitle'>Error</span>
    <p class='sew_errorBody'></p>
    <div class="sew_ajax_error_footer">
    <input type="button" value="OK" id="sew_errorOKButton" />
            </div>
        </div>
    <div id="sew_resultView">
    <div id="sew_resultHeader">
            </div>
    <div id="sew_resultList">
    <div id="sew_resultItemTEMPLATE" class="sew_resultItem">
    : ${FileType}:
    <span class="sew_resultItemLink"><a href="${UriStringWithKeywords}">${Title}</a></span>
    ...
    ...
    ...
                  
                </div>
            </div>
            </div>
    <div id="sew_resultFooter"></div>
        </div>