This post reviews high-level steps for implementing rich, yet lightweight UX customizations in SP using only the Javascript Client Side Object Model, without SPServices, or its underlying library, JQuery. Commonly found Type-ahead functionality, driven from a SP List is used as an example.
JQuery and SPServices have become a very common set of tools to customize and extend the SharePoint UI. This post challenges a growing knee-jerk reaction to reach for large Javascript libraries by exposing what is possible with the built-in SharePoint Jscript Client Side Object Model.
No JQuery Allowed
I needed to implement a type-ahead feature for a client who did not want to implement the JQuery or SPServices JQuery based Javascript libraries due to performance concerns. I am a fan of both javascript libraries for easing SP UX customizations without server side code, so I was immediately interested; how much harder could it be? Would the tradeoff in smaller download footprint come at the expense of slower CSOM performance?
The requirements for the type-ahead feature were nothing new – display a floating div of filtered/suggested items after entering and releasing the 3rd character, but the response time of the type ahead was important. So I set about on a spike that surprised me enough I thought it was worth sharing.
Additionally, with the Designer Design View gone for good in 2013, along with reworking the 2013 UI to be significantly more web-standards based, Microsoft has sent a clear message that Javascript has been elevated in 2013 as one of the primary toolsets in 2013 for applying client-side UX and branding. So I was doubly motivated to see how much I could get done with as little javascript as possible in 2010.
The Usual Caveats: I had limited time to put together this post, so a high fly-by is necessary with just code snippets and screen shots, but the information provided should be sufficient to get you started on JQuery path for SharePoint UI customizations. The feature is obviously very simple as the spike was limited to an hour.
Why They Don’t Matter: This post is intended to encourage and challenge any fellow SP devs out there who rely on JQuery almost instinctively to dig perhaps a little deeper into the built-in Jscript CSOM to validate if the cost of implementing Jquery and larger tooling libraries is always necessary or required. It also showcases the performance of the built in CSOM. Relevant steps and code snippets are called out.
The Result
Download Video (333KB, WMV)
An Orders List was created to contain a list of records to be retrieved after releasing the 3 character entered in an Orders Filter Text Box, whereupon a floating DIV appears with a list of all Order records beginning with the 3 entered characters.
Typing in 2 characters, nothing happens:
Typing in the 3rd character triggers an asynchronous query using the CSOM, which returns and displays a list of Hyperlinks, each of which will cause an HTTP Get Request via query string parameters to reload the page with the selected record.
The response time was better than expected. While I did not measure with FireBug’s Profiler, I tested from a client workstation joined to my network, hitting a VM on a separate host via wireless, and the response time felt well under 25 ms max – while not a completely object measure, I have some experience with the latency inherent in midi controllers and samplers, and the delay was certainly far below the noticable 30-35 ms threshold of slow midi response times.
Deleting characters, or any lack of results hides the floating results DIV. (Not implemented was code to handle onblur).
The Steps
The Data
A custom list, Orders, was created
The Page
A custom page was created in a Pages Library using Designer, the Orders Web Part added, an HTML label and input text box above the web part:
The Markup
<span style="margin-top:20px;font-size:10pt;display:inline-block;padding-bottom:5px;"> Input search terms...</span>
<br />
<div class="ac-outer"> <input name="srchText" id="srchText" class="ac-tb" type="text" onkeyup="keySrch(this)" />
<div id="srchRes" class="ac-dd"></div>
</div>
Key DOM Elements (by Id):
srchText: the input control used to capture the entered characters
srchRes: This is the DIV that will be used to float the results. In times past, I might have dynamically appended this to the DOM, but in recent years browsers seem to prefer that hidden elements to be made visible and floated for dialogs, etc, need to be statically declared prior to onload.
The JavaScript
Almost all code was taken from the excellent examples provided by MS at Common Programming Tasks in the JavaScript Object Model
The following Javascript block was inserted, whose functions will be discussed, referenced by Line Number:
1: <script type="text/javascript" language="javascript">
2: // closes autocomplete search drop down
3: function closeSrch() {
4: var sr = document.getElementById("srchRes");
5: sr.innerHTML = "";
6: sr.style.display = "none";
7: }
8: // invoked on keyup event of input box
9: // invokes setTimeout to srch(), sync call to closeSrch()
10: function keySrch(obj) {
11: var len = 0;
12: var val = null;
13: var sr = document.getElementById("srchRes");
14: key = event.keyCode;
15: if (key == 27) {
16: closeSrch();
17: return;
18: }
19: if (obj != null && sr != null) {
20: val = obj.value;
21: if (val != null) {
22: len = val.length;
23: if (len <= 1) {
24: closeSrch();
25: }
26: if (len > 1) {
27: setTimeout("srch()", 33);
28: }
29: }
30: }
31: }
32: // async call invoked by keySrch
33: // invokes retrieveListItems
34: function srch() {
35: var sr = document.getElementById("srchRes");
36: var st = document.getElementById("srchText");
37: if (sr != null && st != null) {
38: retrieveListItems(st.value);
39: }
40: }
41: // queries list
42: function retrieveListItems(val) {
43: var clientContext = SP.ClientContext.get_current();
44: // ************************
45: // replace with name of your list
46: // ************************
47: var listName = "Orders";
48: var oList = clientContext.get_web()
49: .get_lists()
50: .getByTitle(listName);
51: var camlQuery = new SP.CamlQuery();
52: camlQuery.set_viewXml('<View><Query><Where><BeginsWith><FieldRef Name=\'Title\'/>' + '<Value Type=\'Text\'>' + val + '</Value></BeginsWith></Where>' + '</Query><RowLimit>5</RowLimit><OrderBy><FieldRef Name=\'Title\'/></OrderBy></View>');
53: this.collListItem = oList.getItems(camlQuery);
54: clientContext.load(collListItem);
55: clientContext.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
56: }
57: // populates floating div 'search box' with query results
58: function onQuerySucceeded(sender, args) {
59: var sr = document.getElementById("srchRes");
60: sr.innerHTML = "";
61: var listItemInfo = '';
62: var listItemEnumerator = collListItem.getEnumerator();
63: var val = null;
64: while (listItemEnumerator.moveNext()) {
65: var oListItem = listItemEnumerator.get_current();
66: val = oListItem.get_item('Title')
67: var url = document.location.href.replace(document.location.search, "")
68: //****************
69: // replace with the markup of your choice
70: // should be refactored to generate output more cleanly
71: //****************
72: listItemInfo += "<div style=\'width:100%\'><a class=\'csglink1\' href=\'" + url + "?fsrch=" + val + "\' target=\'_self\'>" + val + "</a></div>"
73: }
74: if (listItemInfo.length > 0) {
75: var sr = document.getElementById("srchRes");
76: sr.innerHTML = listItemInfo;
77: sr.style.display = "block";
78: }
79: }
80: // displays alert - copied from technet
81: // recommend display errors using SP.UI.ShowModalDialog
82: function onQueryFailed(sender, args) {
83: alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
84: }
85: </script>
Breaking It Down
The Flow
Capturing the KeyUp event (keySrch function, line 11)
With every keyup event in the input box;
- If more than 3 chars present in textbox, use SetTimeout to fire Srch function (Line 34). Srch() is invoked asynchronously SetTimeout to allow the onkeyup event to continue without any noticeable lag or delay.
“Cheater” Note: By using a plain HTML input element, I was able to use an ID attribute value that would remain unmollested by not participating in the asp.net control hierarchy, allowing use of the efficient getElementById (Line 13). In a more mature implementation with an asp.net control participating in the control hierarchy, I would have briefly mourned for the inability to deploy JQuery, and then just mimicked one of the many methods SharePoint’s native Jscript codebase uses to identify and find the input control, noteably getElementByTagName, with a loop and a break;
Fire Async “AJAX” Search Query Using Jscript CSOM
The Javascript function Srch() (Line 34), invokes one of the 2 most important functions of this spike, retrieveListItems() (Line 42)
retrieveListItems is the function where the use of the SharePoint Jscript CSOM comes to play, where
- a CAML query is built using the SP.CAMLQuery class, and executed against the Orders List, ordered by Title and limited to 5 results Lines 52)
- The query is fired asynchronously using the executeQueryAsync method of the clientContext object (Line 55)
- Note: the executeQueryAsync method requires passing in delegates to callback functions necessary to retrieve the results, or handle failure in this case OnQuerySucceeded and OnQueryFailed
Processing the Results – Building Links with QueryString Parameters for HTTP Gets
OnQuerySuceeded() (Line 58): This is where the demo nature of this code snippet shines proudly, but gets it done.
This demo was about using the non-JQuery based type-ahead lookup feature to drive the filtering of some connected web parts, although your needs may differ.
As the code demonstrates, the returned results are converted into a set of hyperlinks pointing back to the same page, with a custom querystring parameter, which is used by a hidden filter web part on the page to drive filtering other web parts. While filtering web parts may not be your particular end goal, the general approach of dynamically generating hyperlinks with custom query string parameters to drive further actions in the SharePoint UI is a common approach invoking action based on selected hyperlinks.
Of note are lines 65, where the method getEnumerator() is invoked to return an array of results, line 72, where the result HTML is formatted, and the contents of the floating results div are populated (inner HTML) for display (Line 76).
Styling and Formatting
Of course some CSS was required, which is included in the snippet below, and which should naturally be placed in a CSS file and deployed to the Style Library or 14 hive, depending on your needs:
<style type="text/css">
<style type="text/css">
a.csglink1 {line-height:18px;color:#0072bc;text-decoration:none;padding-top:5px; padding-left:3px;}
a.csglink1:hover{ text-decoration:underline; }
.ac-outer { position:relative;width:208px; }
.ac-tb { wdth:200px;}
.ac-dd { width:203px;position:absolute;display:none;z-index:99;background-color:#ffffff;top:26px;border-top:1px gray solid;border-bottom :1px black solid;border-left:1px black solid;border-right:1px gray solid;}
</style>
Only The Beginning
This post demonstrated how it is possible to implement rich client UI features such as ajax-style type-ahead lookups in the SharePoint interface using only the Jscript CSOM and a little HTML and CSS elbow grease, without the need for deploying Jquery or it’s more husky descendent, SPServices. One of my initial primary concerns with the CSOM was performance. While I have not tested this particular solution over a 56K baud modem against a list of 10000 records on a heavily trafficked site while streaming nyan cat videos in HD, I was definitely surprised with the response times of the API.
JQuery will remain a valuable tool for customizing the SharePoint UI, but I won’t be reaching for it as reflexively as I have in the past.
With the CSOM’s exposure to the page, ribbon, web parts, lists, web app data and more, together with Microsoft’s push for using Javascript as a primary toolset for customizing 2013, I think the role of Javascript and the adoption of the CSOM for implementing rich UX client experiences in SharePoint is only just beginning.