Monday, July 18, 2016

PnP JS Core: The Next Gen SharePoint JavaScript Library

If you develop on SharePoint and you haven’t used the PnP JS Core Library, you’re missing out on clean, and really efficient JavaScript.  I am not going to explain the entire PnP project but the team describes the JS Core library below.

The Patterns and Practices JavaScript Core Library was created to help developers by simplifying common operations within SharePoint. This is aligned with helping folks transitioning into client side development in support of the upcoming SharePoint Framework. Currently it contains a fluent API for working with the full SharePoint REST API as well as utility and helper functions. This takes the guess work out of creating REST requests, letting developers focus on the what and less on the how.

Now there are several ways to load the JS Core Library depending on your development setup, but the quickest way to get started is just downloading the library and loading it like other libraries (such as jQuery).

image

Something very important to understand about this library and that much of its API requires the use of Fetches and Promises.  Since Internet Explorer does not support either of these, you will need to load two more libraries in order to use the PnP library.  You can access the promise library here and the fetch library here.  If you get hung up on any of this make sure you check the JS Core Wiki on Github

Writing Better Code

Ok so lets cover some very basic stuff you can do with the PnP library.  First, we will compare a traditional REST call to get information from a list to how you can do it in PnP.  Making a typical call to a SharePoint list might look very similar to the snippet below.  We have to specify the web, the api we want to use, and in this case I am filtering based upon the current user.  We have to specific the type of call, the headers, and then what to do on either success or failure.  Now there isn’t anything wrong with the jQuery below but if you have a lot of stuff that needs to be done on the page, your code base could easily grow too large to manage. 

 $.ajax({  

  url: _spPageContextInfo.webAbsoluteUrl + '/_api/web/lists/getbytitle(\'doc list\')/Items?$filter=AuthorId eq ' + _spPageContextInfo.userId,  

  type: "GET",  

  headers: {  

   "accept": "application/json;odata=verbose",  

   "X-RequestDigest": $("#__REQUESTDIGEST").val(),  

   "content-Type": "application/json;odata=verbose"  

  },  

  success: function (data) {  

   jQuery(data.d.results).each(function (i) {  

    console.log("Lets Do Stuff With Returned Data");  

   });  

  },  

  error: function (error) {  

   alert(JSON.stringify(error));  

  }  

 });  

Doing this in PnP becomes incredibly easy.  You simply specify the correct namespace and how you want the data returned.  Filtering is super easy as well as selecting specificy columns.  If we wanted to only return a specific column we would just add .select() after the .items to only select the columns we want returned. 

 $pnp.sp.web.lists.getByTitle("Documents").items.filter("AuthorId eq 'Current user'")get().then(function (data) {  

  data.forEach(function (data) {  

      console.log(data);  

    });  

 });  

Ok so that might not blow you away but this alone is huge in my eyes. The part that’s going to make your fingers tingle is the batching capability PnP JS Core Introduces.

Batching Is Awesome

PnP introduces the capability of batching requests into 1 single request.  One particular problem I ran into on a project was I had a SharePoint list that had the possibility of having over 1,000 items.  Each would represent a document inside a document library.  I needed to fetch the list items, iterate through each to get some metadata, and then check the document library locations to see if a document existed. When the page where the JS was executed loaded, we were seeing up to a 7-8 second load time to check only 100 list items.  Batching saved the day on this one.  

Lets Look At Batch Code

Creating batches is pretty simple, especially if you already understand promises.  In the code below we are fetching the list items via CAML query so we can pull back the managed meta data field values in one fell swoop.  Once the data is returned, I create a lightweight object with the values I need and then begin the promise chain. 

$pnp.sp.web.lists.getByTitle("Placeholder").getItemsByCAMLQuery({ ViewXml: "<View><Query><Where><IsNotNull><FieldRef Name='ID' /></IsNotNull></Where></Query><ViewFields><FieldRef Name='Filename' /><FieldRef Name='DocumentType' /><FieldRef Name='Exists' /></ViewFields></View>" }) .then(function (data) { //create new array with data from list var newArray = []; data.forEach(function (index) { varinsert = { "documentType": index.DocumentType.Label, "url": "", "exists": "false", "fileName": index.Filename, "ID": index.ID, }; newArray.push(varinsert); }); //create chained promises to ensure data is checked before its displayed on the page var batchedPromises = getBatchedPromises(newArray, getPlaceholderPromises); completePromises(batchedPromises, groupItems); });

 

 

 

So the batching is going to occur before the REST call will.  Creating a batch is as simple as calling the code pnp.sp.createBatch(); before making your REST call.  In the code below, we are simply creating a batch for every 15 items in the Array we passed into it.  Once we have our returned promise, our next code block is executed.

 var getBatchedPromises = function (items, promiseCallback) {  

   var promises = [];  

   var b = null;  

   var itemsCount = items.length;  

   items.forEach((i, index) => {  

     if (index === 0) {  

       b = $pnp.sp.createBatch();  

     }  

     if (index % 15 === 0 && index !== 0) {  

       b.execute();  

       b = $pnp.sp.createBatch();  

     }  

     var p = promiseCallback(i, b);  

     promises.push(p);  

     if (itemsCount === index + 1) {  

       b.execute();  

     }  

   });  

   return promises;  

 };  

Now that the batch has been created, we want to send this batch to our next method to check if the file actually exists in SharePoint.  You will notice that in our PnP Get request, we are using caching (which by default stores the data in the session store for 30 seconds) and .inBatch to pass through the batch that we created in the previous method.  What we end up with is awesome.

 var getFile = function (item, batch) {  

   return new Promise(function (resolve, reject) {  

     var fileName = item.fileName;  

     var docType1 = item.documentType;  

     docType1 = docType1.replace(/[^A-Z0-9]/ig, '');  

     var libraryUrl = _spPageContextInfo.webServerRelativeUrl + "/" + docType1;  

     $pnp.sp.site.rootWeb.getFolderByServerRelativeUrl(libraryUrl).files.usingCaching().inBatch(batch).getByName(fileName).get().then(function(data) {  

       item.exists = "True";  

       item.url = data.ServerRelativeUrl;  

       item.status = data.Status;  

       item.scope = data.Scope;  

       item.duedate = data.DueDate;  

       item.doctitle = data.DocTitle;  

       item.phase = data.Phase;  

       item.block = data.Block;  

       { resolve(item); }  

     }).catch(function(e) {  

       { resolve(item); }  

     });  

   });  

 };  

The Result of Batching

When the batch successfully runs, you will see the $batch in the console whenever a batch call is made.  When you inspect the headers you can find the individual url’s that are included with each batch.  The best part is the time it takes.

image

It was taking around 7-8 seconds to call the list, iterate through the items, and then call the document libraries to see if a document exists and update the html on the page.  With the new batching implemented, it all takes less than 1 second.  Scaled up to hundreds of list items it still takes 1 second on average.
image

Final Thoughts?

The performance results from using the PnP JS Core library alone is worth it.  The fact that you can reduce your code base by potentially hundreds of lines of code means its also much easier to maintain and troubleshoot.  If you haven’t had a chance to use this library then now is the time.  As more improvements are made, the library will only become better.  One important thing to call out is the API documentation is not the best.  In the server-root folder of the project, the scratchpad.js contains a list of all the available methods commented out.