April 26, 2013

A "Smart Drop-Down" for SharePoint


This article is about providing a super smart drop-down, that helps users quickly find and access your key content, even if you have hundred of pages to offer. Technology involved: SharePoint list; Client Side Object Model (CSOM), JQuery.

If the content hosted on your site cannot easily be found and accessed by users, then let's face it: you have wasted your time and your company's money

Making your content findable is not easy though, and you only have 3 technical solutions:

1) Your enterprise search engine kind of works, but half of your users are not happy with it (mmmmh... is it because you never bothered thinking of / testing what keywords your occasional visitors may try to use?...)
2) Navigation menus are great, but you don't own the navigation tree from the top down, so unless the visitor is already "close" to your site (say they are visiting a related parent site), they can't navigate their way down to your content.
3) Once they have finally reached your site, most site owners offer a "quick links" drop-down box to help visitors find popular content. Problem is, past 5 items or so, the drop-down becomes unusable because it takes too much time to read.


Before
Figure 1

This is your typical "quick links" drop-down. It works just fine, but only if you have a handful of items to offer.





After
Figure 2
Our "Smart Drop-Down" offers many advantages:
- group items by categories (see Figure 2, where we are using Recommended", "Most popular" and "Others" categories)
- use html for each items, or even for the categories
- users can type keywords, and the list reduces in real time (type-ahead). So it doesn't matter if you have hundreds of items!
- each items can have synonyms. So users can use other keywords than the ones displayed in the list. For example the last item ("iPad Getting Started") can be found even if user types "help" (see Figure 3)

Synonyms
Figure 3






How did we do it?

Most of the logic relies on Igor Vaynberg's code SELECT2, an excellent JavaScript library which transforms a regular HTML SELECT drop-down into a cool type-ahead drop-down.
All the drop-down items are placed in a SharePoint list. The same list contains the synonyms that users may think of using. See below for the details about this list.
That's pretty much it! The code attached below simply loads the SELECT2 library and CSS, loads the list items using Client Side Object Model (CSOM) and performs a few formatting operations to take care of the categories and the synonyms.
A quick note about CSOM: if you read my previous posts, I was (still am) a big fan of SP_API library which is using standard SOAP operations to query SharePoint list web services. But I'm using CSOM more often, as it shows better performance and allows for more operations not available through web services.
Let’s start with the list. Name your list “Quick Links” and add the following columns:
List_Columns
Column Category:Column_Category Column Synonyms:Column_Synonyms

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<link href="select2.css" rel="stylesheet" />
<script src="select2.js" type="text/javascript"></script>
<style>
.select2-result-label {
 font-size: 12px;
 padding-top: 0px;
 padding-bottom: 0px;
}
.select2-match {
 font-weight: bold;
 text-decoration: none !important;
}
</style>

<!-- SELECT DROPDOWN PLACEHOLDER -->
<div id="smartDropDownContainer"  style="MARGIN: 15px; padding:15px;BORDER-COLLAPSE: collapse;background-color:  #13709D">
 <select id="idSmartDropDown" class="combobox input-large" name="normal" style="WIDTH: 240px;" onchange="smartGoTo(this);">
 <option>
 </option>
</select>
</div>

 <script>
// *** ******************************************************** ***
// *** RETRIEVE LIST ITEMS USING SHAREPOINT CLIENT OBJECT MODEL ***
// *** ******************************************************** ***

// *** Settings
var siteUrl = '/departments/TGSNew/workspaces/TGSIAATT'; // the url of the site containing the list
var listTitle = "Quick Links";
var camlQuery = "";  // if empty string, then we use "SP.CamlQuery.createAllItemsQuery()"
var useCategories = true; // use "Category" column of the list to group items together
var displayNumberForCategories = true;  // Category names start with a "number" for ordering (exmaple: "5 - Most Popular"). This setting indicates if the number must be displayed.

// *** Change the values if you are not using these names for your columns
var columnTitle = "Title";
var columnURL = "URL";
var columnSynonyms = "Synonyms";
var columnCategory = "Category";

// *** Other global variables (do not change)
var listItems;
// ---> EVERYTHING STARTS HERE:
ExecuteOrDelayUntilScriptLoaded(retrieveAllListProperties, "sp.js");

// *** Retrieves items from "listTitle"
function retrieveAllListProperties() {
var clientContext = new SP.ClientContext(siteUrl);
    var oWebsite = clientContext.get_web();
    var myList = oWebsite.get_lists().getByTitle(listTitle);
    camlQuery = '<View><Query><Where><IsNotNull><FieldRef Name="' + columnURL + '" /></IsNotNull></Where>';
    camlQuery += '<OrderBy><FieldRef Name="' + columnCategory + '" Ascending="True" /></OrderBy></Query></View>';
    if (camlQuery=="") {
    query  = SP.CamlQuery.createAllItemsQuery();
     } else {
    query  = new SP.CamlQuery();
query .set_viewXml(camlQuery);
     }

 listItems = myList.getItems(query );
 
    clientContext.load(listItems);
    clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuickLinksQuerySucceeded), Function.createDelegate(this, this.onQuickLinksQueryFailed));
}

// *** This function is called asynchronously if the request is successfull
function onQuickLinksQuerySucceeded(sender, args) {
    var listInfo = '';
    var listEnumerator = listItems.getEnumerator();
// Loop through all the items in the list
var previousGroup ="";
var currentGroup ="";
var firstGroup = true;
var selectHTML="";
    while (listEnumerator.moveNext()) {
        var oneItem = listEnumerator.get_current();
var strDisplay = oneItem.get_item(columnTitle);
var syn = oneItem.get_item(columnSynonyms);
currentGroup = oneItem.get_item(columnCategory);
if (!displayNumberForCategories) currentGroup = startStringAfter(currentGroup, ".");
if (previousGroup!=currentGroup) {

if (!firstGroup) selectHTML+="</optgroup>";
selectHTML += "<optgroup label='" + currentGroup + "' data-HTMLVersion='" + escape(currentGroup) + "'>";
previousGroup = currentGroup;
firstGroup = false;
}

selectHTML += "<OPTION data-synonyms='"+ escape(syn) +"' data-HTMLVersion='" + escape(strDisplay) + "' value='" + escape(oneItem.get_item(columnURL).get_url()) + "'>" + strDisplay + "</OPTION>";

   
    }  // end while (listEnumerator.moveNext())
    if (!firstGroup) selectHTML+="</optgroup>"; 
    $("#idSmartDropDown").append(selectHTML);
    
    // This line calls the code to transform the SELECT into the Smart Dropdown
$('#idSmartDropDown').select2({
 placeholder: "Select or type something",
 allowClear: true,
     matcher: function(term, text, opt) {
    // consider also the synonyms for matching results
        return text.toUpperCase().indexOf(term.toUpperCase())>=0
            || unescape(opt.attr("data-synonyms")).toUpperCase().indexOf(term.toUpperCase())>=0;
    },
    formatResult: function(item) { 
    // Display HTML version (stored as an attribute), instead of actual value
    return unescape(item.element[0].getAttribute('data-HTMLVersion')); 
     }
});      
}

function smartGoTo(selectedObj) {
 var idx = selectedObj.selectedIndex;
 var mySelection = selectedObj.options[idx].value;
 window.location = unescape(mySelection);
}

// This function strips a string from the left part of a given character
function startStringAfter(myString, searchForCharacter) {
 // The separating character is typically in the first 3 characters
 var idx = myString.substring(0,3).indexOf(searchForCharacter);
 var sReturn = myString;
 if (idx!= -1) sReturn = myString.substring(idx+1);
 return sReturn;
}
function onQuickLinksQueryFailed(sender, args) {
    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}


</script>






Finally, you will need to download and store locally on your SharePoint site these 2 files:

- Select2.css
- Select2.js

Enjoy!


Herve