Call Server-side Code from Javascript

Sunday, February 08, 2015

6

Server-side code and postbacks already seem like remnants of a bygone era.  The next generation of SharePoint developers will likely know no more about them than punched tape computers.  Yet, there are still scenarios where you just can't avoid server-side operations.

I recently needed a way to asynchronously execute a block of server-side code from client-side Javascript.  It turns out there is an ICallbackEventHandler interface for precisely that purpose!

There's a detailed MSDN article on it, which I did not find very easy to digest.  In this post I'll try to boil it down to the essentials by going over a SharePoint example.

Background

I was building a visual webpart to let users update their Quick Links and other User Profile properties.  I stubbed out my methods, whipped up some JSOM to grab the properties, and threw in a snazzy jQuery drag-and-drop control.

Then, I tried to implement the Save button.  Disaster struck.  User profiles are read-only from all client-side APIs.

The Big Picture

This diagram illustrates the flow of execution from client to server and back.  I'll go over each piece in detail, but this is how they all fit together.



Client Side

We'll start from the Javascript side, as that is where the user action begins.  ICallbackEventHandler lets us asynchronously fire off a server side method, to which we can pass a single string parameter.

Let's say my Quick Links are stored in <div>s on the page:

<div class="link" data-title="Google" data-url="http://www.google.com" />
<div class="link" data-title="Bing" data-url="http://www.bing.com" />
<div class="link" data-title="MSDN" data-url="http://www.msdn.com" />

We can push this information into a JSON object, serialize it into a string, and pass it server-side to be saved.

Invoke server-side code


At this point, the ExecuteServerSide method below that will invoke our server-side code isn't defined yet. We will wire it up later from the code-behind.

function save() {
    var links = [];

    // Create JSON object with link data
    $('.link').each(function () {
        var title = $(this).data('title');
        var url = $(this).data('url');

        links.push({ 'Title': title, 'Url': url });
    });

    // Serialize the object to a string
    var strLinks = JSON.stringify(links);

    // Invoke server-side code.  Will wire up later.
    ExecuteServerSide(strLinks);
}

Completion Handler


Next, let's add the method that will be called when the server-side operation completes.  The server can return a single string back to the page.

function ServerSideDone(arg, context) {
    alert('Message from server: ' + arg);
}

Server Side

Begin by adding the ICallbackEventHandler interface to the webpart's code-behind.  This interface has two methods that need to be implemented: RaiseCallbackEvent and GetCallbackResult.

public partial class QuickLinksEditor : WebPart, ICallbackEventHandler

RaiseCallbackEvent


RaiseCallbackEvent( string eventArg ) is the entry point.  It's what's called by ExecuteServerSide in line #16 of the Javascript above.  eventArg is the string passed from the client side (e.g. the serialized link data)  In most scenarios, you would save this string in a class variable and use it later to perform whatever server-side operation.

We could parse out the links manually, but it's cleaner to make a simple data model class:

[DataContract]
public class QuickLink
{
    [DataMember]
    public string Title;

    [DataMember]
    public string Url;
}

Then we can take advantage of DataContractJsonSerializer to convert the JSON string to a List of QuickLink objects:

public void RaiseCallbackEvent(string eventArg)
{
    // Instantiate deserializer
    DataContractJsonSerializer serializer =
       new DataContractJsonSerializer(typeof(List<QuickLink>));

    // Deserialize
    MemoryStream stream =
       new MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(eventArg));

    // Save input data to class variable for later processing
    this.Links = (List<QuickLink>)serializer.ReadObject(stream);
}

GetCallbackResult


This method is where we execute the server-side operation.

It returns a string, which is how information gets passed back to the client-side.  That return value is the ServerSideDone method's arg parameter in line #18 in the Javascript code above.

public string GetCallbackResult()
{
    try
    {
        // Get User Profile Manager and update Quick Links
        // Full code at bottom of article
    } 
    catch(Exception ex)
    {
        return ex.Message;
    }

    return "Success!";
}

Wire Up

Finally, we wire up the two client-side functions, ExecuteServerSide and ServerSideDone.  That is done from Page_Load in the code-behind:

protected void Page_Load(object sender, EventArgs e)
{
    ClientScriptManager scriptMgr = Page.ClientScript;

    // Completion handler
    String callbackRef = scriptMgr.GetCallbackEventReference(this, 
        "arg", "ServerSideDone", "");

    // Invoke server-side call
    String callbackScript = 
        "function ExecuteServerSide(arg, context) {" + 
        callbackRef + 
        "; }";

    // Register callback
    scriptMgr.RegisterClientScriptBlock(this.GetType(), 
       "ExecuteServerSide", callbackScript, true);
}

Again, note that line #7 here defines the method signature in line #18 of the Javascript.  And line #11 here defines the method called in line #16 of the Javascript.

That's it!

Complete Code-behind


using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Microsoft.Office.Server.UserProfiles;
using Microsoft.Office.Server;

using Microsoft.SharePoint;

namespace SharePointificate.QuickLinksEditor
{
    [DataContract]
    public class QuickLink
    {
        [DataMember]
        public string Title;

        [DataMember]
        public string Url;
    }

    [ToolboxItemAttribute(false)]
    public partial class LegacyQuickLinks : WebPart
    {
        private List<QuickLink> Links;

        public LegacyQuickLinks()
        {
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            InitializeControl();
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            ClientScriptManager scriptMgr = Page.ClientScript;

            String callbackRef = scriptMgr.GetCallbackEventReference(this, 
                "arg", "ServerSideDone", "");

            String callbackScript = 
                "function ExecuteServerSide(arg, context) {" + 
                callbackRef + 
                "; }";

            scriptMgr.RegisterClientScriptBlock(this.GetType(), 
                "ExecuteServerSide", callbackScript, true);
        }

        public string GetCallbackResult()
        {
            try
            {
                SPServiceContext serviceContext = 
                    SPServiceContext.GetContext(SPContext.Current.Site);

                UserProfileManager userProfileManager = 
                    new UserProfileManager(serviceContext);

                UserProfile currentUser = 
                    userProfileManager.GetUserProfile(true);

                QuickLinkManager quickLinkManager = currentUser.QuickLinks;

                quickLinkManager.DeleteAll();

                foreach (QuickLink link in this.Links)
                {
                    quickLinkManager.Create(link.Title, link.Url, 
                        QuickLinkGroupType.General, null, Privacy.Public);
                }
            } 
            catch(Exception ex)
            {
                return ex.Message;
            }

            return "Success!";
        }

        public void RaiseCallbackEvent(string eventArgument)
        {
            DataContractJsonSerializer serializer = 
                new DataContractJsonSerializer(typeof(List<QuickLink>));

            MemoryStream stream = 
                new MemoryStream(ASCIIEncoding.ASCII.GetBytes(eventArgument));

            this.Links = (List<QuickLink>)serializer.ReadObject(stream);
        }
    }
}


Bulk Load List Items with Powershell

Sunday, February 08, 2015

0

I constantly tear down and rebuild sites in my dev environment, and re-populating lists with test data is a royal pain.  One day, as CEO, I'll have "people" to take care of this sort of thing.  Alas, I have but my own wits to rely on for now.

The most concise and flexible solution I could find is using Powershell to load the data from a CSV file.  I usually create the list data in Excel, and Save As a .CSV file.  It's a huge time saver.  The first line of the file specifies the display names of the list fields to populate.

For example, this CSV data is for a Links list.  Note the URL field in all caps.  The display names are case sensitive.

Title,URL,Description
SharePointificate,http://sharepointificate.blogspot.com,A very cool blog
Funny Cat Videos,http://funnycatvideos.net,Just click it.
Angry Birds,https://www.angrybirds.com,U mad?

There's an auto-magic Import-CSV Powershell cmdlet that loads a CSV file into a list of objects.  The properties of each object correspond to the display names defined in the file's header line.

The links CSV above would produce a list of 3 objects.  Each object will have a .Title, .URL, and .Notes property.  The following code outputs "A very cool blog":

$data = Import-CSV links.csv
Write-Host $data[0].Notes

We can then iterate over each object's properties with .psobject.properties, and map their Name / Value to a new SPListItem.

The sample script below only handles field types where we can directly set the text value.  Some complex types like Taxonomy or Multichoice will require special logic.  To handle those cases, check $list.Fields[$csvField.Name].Type.  Refer to this Technet article for code samples for setting every type of SharePoint field.

param(
    [string]$WebUrl = $(Read-Host -prompt "Web Url"),
    [string]$ListName = $(Read-Host -prompt "List Name"),
    [string]$CsvPath = $(Read-Host -prompt "Path to CSV file")
)


# Load SP snapin if needed

$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
if ($snapin -eq $null) 
{
    Add-PSSnapin "Microsoft.SharePoint.Powershell"
}

$web = Get-SPWeb $WebUrl
$list = $web.Lists[$ListName]; 

# Load CSV into object

$csvItems = Import-Csv $CsvPath

# Iterate over items to import

foreach($csvItem in $csvItems)
{
    # Create new list item
    $newItem = $list.items.add();
 
    # Iterate over fields to import
    foreach($csvField in $csvItem.psobject.properties)
    {
        if(![string]::IsNullOrEmpty($csvField.Value))
        {
            # Set field value
            $newItem[$csvField.Name] = $csvField.Value;
        }
    }

    $newitem.update();
}


Bootstrap Responsive Framework with SP 2013

Saturday, February 07, 2015

7

Responsive UIs are all the rage these days, but SharePoint hasn't caught up just yet.  Maybe in SP15.  One can hope. In the meantime, Bootstrap UI is my go-to solution.  It's one of the most robust and mature (and free!!) responsive frameworks.  It has no external dependencies and integrating with SharePoint is as simple as including a single CSS and JS file in your masterpage.

However, Bootstrap's CSS overrides some important SharePoint classes.  This article covers CSS fixes to make the two play nicely together.  They are just some of the more common issues, and are by no means an exhaustive list.  If you discover others, please do share in the comments!

These changes were tested on a SP 2013 Publishing Site with Bootstrap v3.3.2.

Page Width

As we narrow the browser window on a responsive site, we expect to eventually hit the mobile breakpoint.  On a SP site, that may never happen.  At some point, horizontal scrollbars crop up and the actual content window stops getting smaller.

This is because one of the onion-like layers of container elements has a min-width restriction, which needs to be removed:

#contentBox {
    min-width: 0;
}

Text Styles

Bootstrap's line-heights and font sizing messes with SP menu and modal dialog text.

.ms-dlgContent {
    line-height: 1.1;
}

#s4-ribbonrow {
    line-height: 1.2;
}

.ms-core-menu-link:hover,
.ms-core-menu-link:focus,
.ms-core-menu-link:active,
#s4-ribbonrow a:hover,
#s4-ribbonrow a:focus,
#s4-ribbonrow a:active {
    text-decoration: none;
}

#pageStatusBar,
.ms-cui-tooltip {
    line-height: 1.1;
    font-size: 8pt;
}

.ms-cui-modalDiv-ie,
.ms-cui-glass-ie {
    background-color: transparent;
}

Box Sizing

Box-sizing is an innocent-looking CSS property that can sow utter chaos and ruin on a page.  It specifies whether an element's width and height include the padding and border (border-box), or just the inner content (content-box).

Content-boxing is the default, which is what SharePoint's CSS is built to work with.  Bootstrap, however, requires border-box and sets that pretty much globally.  This doesn't completely break SharePoint, but most controls will be mis-aligned or cut off.

We have to allow Bootstrap to use border-box, while reverting just the SharePoint elements to content-box.  I started with Olly Hodgson's fixes for SP2010, and added some classes for 2013:

#s4-ribbonrow *,
#s4-ribbonrow *:before,
#s4-ribbonrow *:after,
#ms-help *,
*[class*='ms-core-menu'],
*[class*='ms-dlg'],
*[class*='ms-dlg']:before,
*[class*='ms-dlg']:after,
.ms-dlgFrameContainer > div,
.ms-dlgFrameContainer > div:before,
.ms-dlgFrameContainer > div:after,
.ms-dlgFrameContainer > div > div,
.ms-dlgFrameContainer > div > div:before,
.ms-dlgFrameContainer > div > div:after,
.ms-MenuUIPopupBody,
.ms-MenuUIPopupBody:before,
.ms-MenuUIPopupBody:after,
.ms-MenuUIPopupBody *,
.ms-MenuUIPopupBody *:before,
.ms-MenuUIPopupBody *:after,
.ms-ToolPaneOuter,
.ms-ToolPaneOuter:before,
.ms-ToolPaneOuter:after,
.ms-ToolPaneOuter *,
.ms-ToolPaneOuter *:before,
.ms-ToolPaneOuter *:after,
*[class*='ms-cui'],
*[class*='ms-cui']:before,
*[class*='ms-cui']:after,
*[class*='ms-cui'] *,
*[class*='ms-cui'] *:before,
*[class*='ms-cui'] *:after,
*[class*='ms-dlg'] *,
*[class*='ms-dlg'] *:before,
*[class*='ms-dlg'] *:after,
*[class*='ms-webpart'] * {
    -webkit-box-sizing: content-box;
    -moz-box-sizing: content-box;
    box-sizing: content-box;
}


Content Search Webpart Dynamic Filtering

Wednesday, August 13, 2014

13

The Content Search webpart (CSWP) introduced in SharePoint 2013 is an extremely powerful and flexible tool for displaying dynamic content.  It can surface any content from the Search index, and display templates allow us complete control over how the results are rendered.

Each CSWP is associated with a search query which drives its content.  This post will focus on scenarios in which the search query itself needs to be dynamic.  The techniques in this post can be applied to the Search Results webpart as well.

There are several approaches to making the webpart's query dynamic:
  1. Use built-in query variable tokens
  2. Extend the CSWP with server side code
  3. Dynamically update the query with Javascript

Query Variables

The CSWP supports a set of predefined variables that can be used to build dynamic queries.  The variable tokens are replaced with actual values when the CSWP loads.  There's a plethora of variables to choose from--some of the more useful ones are Page Field, Site Property, User, URL Token, and Query String.

Say we have a page called Orders.aspx, with a CSWP that displays Orders.  We want to limit its results to orders for a specific Product, to be specified in the query string.  For instance, Orders.aspx?ProductID=3 should display only Orders with a ProductID of 3.

Assuming we have an Orders content type and a search managed property ProductID, this can be achieved by setting the CSWP's query to:

  contentType:Orders ProductID:{QueryString.ProductID}

Refer to this Technet article for the complete list of variable tokens.

Extending the CSWP

While query variables can handle many common scenarios, sometimes we need a little custom logic, or a field not available in the OOB variables.  What if we want to plug the current year into the query?  Or a value from another SP list?  Or from a user control on the page?

One solution is to extend the CSWP with server side code to intercept and update the query.  I am not going to go over the implementation details as there are already several good blog posts on it.

This article is a great tutorial on extending the CSWP to inject SharePoint Variation Labels into the query.

Dynamically Updating the CSWP with Javascript

If we can't use server side code, or need to dynamically change the results after the page loads, it is also possible to update the query using Javascript.  Note that some of the methods used in this approach are undocumented.  Use at your own risk!

Consider this scenario:  we have a CSWP that displays a list of Orders.  We want to query for orders related to a specific Product, determined by a dropdown list on the page.  When the dropdown selection is changed, the CSWP's results should dynamically update (without reloading the page).

First, add a CSWP to the page and set the default query that should run when the page loads.  In this case, I filter for Orders with Product ID 1.


Next, we will create a drop down box with Product options, and Javascript to update the CSWP on change.  I assume that jQuery is available on the page.  For prototyping purposes, we can just drop the markup in a Content Editor webpart.

To create the dropdown:

<select id="Products">
   <option value="1" selected="selected">Plushies</option>
   <option value="2">Computers</option>
   <option value="3">Unspeakable Items</option>
</select>

And now the good stuff... we attach a change handler to kick off some code when the dropdown is updated.  In the handler, we iterate through all the active data providers, looking for the ones associated with CSWPs.  In this example there would only be one.

Then, update the data provider's query to filter on the selected Product ID.  Finally, we call issueQuery() to execute.  The data provider's associated CSWP should automatically pick up the new results and update its display.

<script type="text/javascript">
  $(document).ready(function () {
    // Attach change event handler to Products dropdown
    $('#Products').change(function () {

      // Get selected product ID
      var prd = $(this).val();

      // Loop through Query Groups
      var groups = Srch.ScriptApplicationManager.get_current().queryGroups;

      $.each(groups, function () {
        // Look for query groups associated with a CSWP
        if (this.displays != null && this.displays.length > 0) {
          if (this.displays[0] instanceof Srch.ContentBySearch) {
            // Update and execute query
            var newQuery = 'contentType:Orders productID:' + prd;

            this.dataProvider.set_queryTemplate(newQuery);
            this.dataProvider.issueQuery();
          }
        }
      });
    });
  });
<script type="text/javascript">

 

Addendum: Targeting a Specific CSWP


I've been asked how to change the query of a specific CSWP, if there are more than one on the page?  In the example above where we search through the query groups, it's impossible to tell which is which.

We can leverage the $getClientControl function for this.  This approach requires using a custom display template.  $getClientControl takes as input any HTML element within a CSWP, and returns the containing CSWP object.

Now, we still have no way to grab an HTML element from inside that specific CSWP.  But if we're using a custom display template, we can tag an element with a custom ID or css class.

For example, give a DIV in the display template a special ID:

<div id="MyCSWP">Some content</div>

Now we can grab the CSWP object with $getClientControl( $("#MyCSWP")[0] ):

var ctrl = $getClientControl( $("#MyCSWP")[0] );

ctrl.get_dataProvider().set_queryTemplate(newQuery);
ctrl.get_dataProvider().issueQuery();

Note that $getClientControl expects an HTML element, not a jQuery object. So, passing $("#MyCSWP") will not work.  It has to be $("#MyCSWP")[0].  Alternatively, use document.getElementById.


MCSD SharePoint 2013

Tuesday, May 13, 2014

0

Every year my review forms are dominated by a gapping void that's the "certifications" checkbox.  Time in the IT world flows differently--many moons passed, mountains were worn to pebbles, and those blown to dust.  As MCTS SP07 became MCTS SP10, my manager's hopeful look turned quizzical, then accusatory. What man would so wantonly forsake his yearly goals?  Oathbreaker, they call me.

Now that MCTS has been replaced by MCSD altogether, the time has come to get off my butt. This is the year I shall be certified.  Better years late than never, right?

I will update this post with tips and resources for the four MCSD SharePoint exams as I go through them.


Taxonomy Columns & SharePoint 2013 List REST API

Monday, May 12, 2014

14

I ran into a strange issue today when using the SharePoint 2013 REST API for Lists with Managed Metadata columns.  For a multi-select taxonomy column, the List service returns Label text as expected.  But if you change that same column to single-select, suddenly an ID is returned in the Label field!

For instance, consider an Orders list with a Product multi-select taxonomy field.  I run the following REST query to dump all the list items:

  http://sharepointificate/_api/web/Lists/GetByTitle('Orders')/Items

All is good and the Product label text is returned:

Now if I just change Product to single-select and run that same query, the friendly label is gone!  This has to be a bug.  Yes, YOU, Microsoft.  I'm talking to you!


Luckily, there is a workaround. The Taxonomy label is correctly returned when we use a CAML query to retrieve the list items.  Erik C. Jordan's answer on this MSDN thread reveals a way to use CAML against the REST API.  This guy must be some kind of SP savant, because I can find absolutely no other documentation on this method—but it works

/GetByTitle('Orders')/GetItems(query=@v1)?@v1={'ViewXml':'<View><Query></Query></View>'}

A couple things to note:

  1. GetItems is a POST operation.  The query above will not work when run from your browser's address bar, which performs a GET.  You need a tool like Fiddler to send a POST message to the REST endpoint.  More likely, you'll be doing a jQuery ajax call like the example below.
  2. An X-RequestDigest message header is required for POST operations against the REST API.  Its value differs between environments and apps.  If this header is missing or incorrect, the server will return a 403 Forbidden error.  When executing within a SharePoint-hosted or on-premise context, it should be available on the page itself, and you can grab it with $("#__REQUESTDIGEST").val().  For other contexts, you'll have to retrieve the value like this MSDN article describes.

The complete Ajax call:

$.ajax({
    url: "http://sharepointificate/_api/web/Lists/GetByTitle('Orders')/GetItems(query=@v1)?@v1={'ViewXml':'<View><Query></Query></View>'}",

    type: "POST",

    headers: {
        "Accept": "application/json;odata=verbose",
        "X-RequestDigest": $("#__REQUESTDIGEST").val()
    },

    success: function (data, textStatus, jqXHR) {
        console.log(data.d.results[0].Product.Label);
    },

    error: function (jqXHR, textStatus, errorThrown) {
        console.dir(jqXHR);
    }
});


Powershell Variable as Property Name

Thursday, May 08, 2014

0

I discovered something beautiful and wonderous today. It's possible in Powershell to use a variable directly as a property name!  For instance:

Windows PowerShell
Copyright (C) 2012 Microsoft Corporation. All rights reserved.

PS C:\> $d = Get-Date
PS C:\>
PS C:\> $prop = "DayOfWeek"
PS C:\>
PS C:\> $d.$prop
Thursday
PS C:\>

Mind blown.


JavaScript Promise vs Deferred

Monday, May 05, 2014

1

jQuery Promises have been around for a while, but I've always slinked away tail between my legs at the mere mention of them. Running might have attracted the beast's attention. Alas, my new project uses Promises religiously. They are actually nifty constructs once you wrap your head around them.  I had to stew and mull for about a week before I could grasp the difference Promises and Deferred (I know, what a scrub)

This post is for anyone specifically wondering about Promises vs Deferred.  I'm not going to talk about Promises in general--there are already a ton of good articles on that topic.

There is really nothing special about Promises and Deferred--they are both plain ol' JavaScript objects.  Every Deferred object has a corresponding Promise object.  The Promise is essentially a copy of the Deferred with limited functionality.

When you are running an async operation, you create a Deferred.  You can then use it to attach handlers, and more importantly, Resolve or Reject when the operation completes.  In the meantime, you get the Deferred's corresponding Promise and return that to your caller.


As the caller, you get back a Promise object with which you can also attach handlers. The difference is that you can NOT trigger those handlers by calling Resolve or Reject.  This makes sense, because the caller wouldn't / shouldn't know about the async operation and when it completes.