Preparing Your Toolbox for the SharePoint Framework

In July, we were at a junction.  We had just wrapped up a new SharePoint On-Premises module with Angular v1.4 and ngOffice UI Fabric.  Our previous module was Angular v1.2 with UI Bootstrap.  We knew the SharePoint Framework was coming, and would even eventually come down to On-Premises in 2017.  Plus, at the rate JavaScript moves, we basically need to refresh our toolbox every 3 months. It was about time to do a bit of research – and so we did.

Choosing a Path

This research culminated in writing the Preparing Your Toolbox for the SharePoint Framework with Angular, Webpack and Kendo UI whitepaper, which is built around this Contract Register demo (GitHub). The technologies we picked focused primarily on what we were familiar with, but also what we knew would be supported in the SharePoint Framework.  We double checked with the examples from many professionals who have been blogging about their SharePoint Dev Kitchen experiences and against all the PnP SPFx videos.

Angular

We started with Angular and Kendo UI with Gulp, but the build process involved modularizing the code and bundling to a CDN, so we added WebPack

Finally, instead of talking to SharePoint directly, or use Kendo UI DataSource components (which also supports SharePoint REST) - I wanted us to use pnp-js-core – an optionally fluent API for talking to the REST endpoint from the Office PnP team.  

We bumped our version to Angular v1.5 because we wanted to be at the best position of being able to "deploy to production" today, but still write to the components spec that will be the norm in Angular v2.  Moreover, the SPFx-webparts will run in the SharePoint Mobile App in the future.  So without explicitly doing too much extra work - we are building toward that future where we build and it runs in the browser and on mobile.

PnP JS Core

As for PnP JS Core - the fluent API was a joy to work with, and the way batching was done is quite magical.  In v1.0.3 the library started to support server-side NodeJS as well, which means you can use the same JavaScript code to interact with your SharePoint from both the browser and the server.  Writing the same code that runs in both places is the holy grail that we always look towards.  If JavaScript can run everywhere, then the libraries we want to use should be able to run everywhere.

WebPack

As we dived deeper into the wonder that is WebPack, its magic unfolded.  In this little demo video we recorded for Contract Register you may notice how fast WebPack injects code into the browser – the app is ready before the Office 365 suite bar appears.  This is a side effect of code splitting - WebPack doesn't just merely concat and uglify the JS file. It blasts JavaScript modules into the browser like a shotgun - all at the same time. WebPack almost removes the need for Gulp in the process. Gulp tasks are still needed to start different tasks - but WebPack does the work underneath, and it does it really well.

TypeScript

We debated a lot about TypeScript.  It would have made the intelli-sense and code checking better, but we decided to leave it out. It would have been that One More Thing to learn, and the list was already getting long.  Have a look at the code we wrote for the contract-register (GitHub) and let us know if we made the right choice.

Kendo UI

I want to talk about Kendo UI as well. I hadn’t used Kendo UI before this project.  My colleague and co-author of this whitepaper, Bart Bouwhuis, is a big fan and he is constantly showing me cool features.  The whitepaper is not an advertisement for Progress (formerly Telerik), but I also think we don't really talk about Kendo UI as much as we ought to. 

My feeling is that the components are really mature – in some areas rivalling or exceeding UI Bootstrap. 

Components like Grid, Window and PDF export were easy to add.  They already support Office 365 theme, so they don't look out of place.  

Telerik supports Angular v1 and React and they just released the developer preview for Angular 2.  We ended up spending very little time making the components work for us – in most cases, we looked up the example on Telerik Docs and it just worked!  An additional blessing is we never needed to tweak a particular CSS to fix some styling problem – that was actually quite a painful issue with UI Bootstrap or ngOfficeUIFabric.

SharePoint Framework (SPFx) Developer Preview

The SPFx developer preview came out during the writing of the whitepaper.  Remember that train?  Turns out it was coming really fast.  We love how fast the SharePoint team is running.  But it means we have a few notes to add.

Firstly, the whitepaper isn't just about SPFx.  It is about SharePoint as a platform with AngularJS and WebPack, and having those pieces ready and aligned for SPFx.  It is about a stable set of core tools that works well together.  It is about a set of tools that runs today on SharePoint 2016, SharePoint 2013 and SharePoint Online.  You can already use these, without having to wait for SPFx.

Secondly, with SPFx there are even more opportunities.  We will have better support for Angular and SPA modes - currently both scenarios are not yet released.  So the story will evolve there and be more awesome.  The contract register demo can run in the SPFx webpart mode - in that case, the code to initialize the app and set up the various lists should go into the web part property panel.  So only the contributor can have access to create the lists. 

How we decided to manage this constant flux is to publish a blog update when SPFx is formally released to fill in the “gaps” – the things unknown as of the time of publishing the whitepaper. So stay tuned for such an update, likely after Microsoft Ignite.  We also have a bunch of crazy ideas listed for the Contract Register.  So if you want to get your hands dirty and have a go, we are taking pull requests!

Conclusion

The time to jump in is always now (or as soon as you can).  Here we present two months of our hardest effort to promote modern web technologies and SharePoint as a platform. 

Please download our whitepaper Preparing Your Toolbox for the SharePoint Framework with Angular, Webpack and Kendo UI, compare it to what you guys use and let us know what you like or love.

Related Resources

Working with SharePoint WebHooks with JavaScript using an Azure Function

This blog post covers additional explanation on how to subscribe to SharePoint WebHooks and have it running with only JavaScript in Azure Functions.  The entire code is in one single JavaScript file.

The SharePoint team delivered on the promise to ship SharePoint WebHooks, and made an reference example in C#.  Be sure to watch the PnP webcast as well.

What is Azure Functions?

Azure Functions, in the simplest sense, is a Azure WebJob that lets you run a JavaScript function in a file (it can do C# too), and it will run it for you when you trigger it.  Because it is a Serverless platform - you don't pay for the WebJob unless your function is running.  This makes it really economical (you also get like a million free runs per month...)

Azure Function comes with a super user-friendly UI and you can paste or upload your JavaScript directly in the browser.  You don't need any tools installed.

Running in the browser

 

Of course, this is server-JavaScript.  So think NodeJS.  We can talk to lots of REST APIs (Graph, SPO) but there will be no browser DOM.

What can you do with this?

You can trigger it on a timer.   Hey that sounds like a SharePoint Timer Job.

You can trigger it on a web request.  This is super useful if you have a client-side UX and you need a button to do some high level elevated permission action.  You call the Azure Function to do it for you, using an App-Only permission elevation.

Why is SharePoint WebHooks important?

A SharePoint WebHook is a REST endpoint that you can attach a remote End Point to.  Right now, there is only a List endpoint.  So any updates to the list items will trigger an event, and SharePoint will call your function.

Hey.  Wait that sounds like a SharePoint Remote Event Receiver.  You are right!

Design

So the idea of our function is this - when triggered:

It will Auth and then talk to SharePoint REST and do one of the following.

  • Check if it has a request parameter "subs" - then it will list the current subscriptions on our target list
  • Check if it has a request parameter "sub" - it will try to attach itself to the target list
    SharePoint will immediately call the function with a validationtoken parameter so…
  • Check if the request has a validationtoken parameter - it will immediately reply with that token as text/plain.
  • Skipping all three conditions, it will run the default action
    The default action is that it will add an list item in a different destination list.  Because the function is running on its own App-Only permission, it can update a list that the original user doesn't have access to.

 

GET Subscriptions

options = {
    method: 'GET',
    uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('subscribe-this')/subscriptions",
    headers: headers
};
request(options, function (error, res, body) {
    context.log(error);
    context.log(body);
    context.res = { body: body || '' };
    context.done();
});

GET subscriptions.  Array result is [] empty by default.  It has subscriptions after you attach hooks successfully.

 

POST Subscription (to add itself)

options = {
    method: 'POST',
    uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('subscribe-this')/subscriptions",
    body: JSON.stringify({
        "resource": "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('subscribe-this')",
        "notificationUrl": "https://johnno-funks.azurewebsites.net/api/poke-spo2?code=q8sq9wxm62asd-YOURTRIGGERCODE",
        "expirationDateTime": "2017-01-01T16:17:57+00:00",
        "clientState": "jonnofunks"
    }),
    headers: headers
};
request(options, function (error, res, body) {
    context.log(error);
    context.log(body);
    context.res = { body: body || '' };
    context.done();
});

Sending POST subscription without handling validation token.  The request fails.

 

Handle Validation Token

// if validationtoken is specified in query
// immediately return token as text/plain
context.log(req.query);
context.log(req.query.validationtoken);
context.res = { "content-type": "text/plain", body: req.query.validationtoken };
context.done();

The function is called twice.  Second time by SharePoint to validate the subscription.

Result Video

The Poked list item was created by "SharePoint App" not "John Liu" the user.

Notes

This demo builds on top of the code from Azure Functions, JS and App-Only Updates to SharePoint Online that covers authentication with certificate, and running AppOnly permissions. 

Additionally, I've moved ClientID and Certifate ThumbPrint to Azure Function App Settings.  This means they are no longer part of the code.

App Settings

var clientId = process.env['MyClientId'];
var thumbprint = process.env['MyThumbPrint'];
Function App Settings > Configure App Settings

Function App Settings > Configure App Settings

 

Source Code

I'm making an effort to put all my demo source code on GitHub going forward.  This is part of the "Upgrade Your JS" demos.

https://github.com/johnnliu/demo-upgrade-your-js/tree/master/azure-function-web-hook

Let me know what you think about SharePoint WebHooks, Azure Functions and JavaScript that will rule everything ;-)

If you spot a bug or want to update the code - send me a Pull Request.

 

All Demo Downloads will be on Github - blog housekeeping

I've taken a short break from writing blog posts - I haven't been idle, I have been writing something.  Hopefully to be able to share it with everyone soon.  Anticipation is killing me.

As we roll into a new month September!  There are a few planned updates I'm doing for the rest of the year...

  1. Several old blog posts that was in Draft will be merged and published.  These are summary posts from the Office 365 Saturday events I've been going to.
  2. I've been in several SharePoint Sydney user group sessions and that needs summaries too.
  3. SPFx is announced, and now Developer Preview.  Posts there too.
  4. I'm looking around to see what's the best way to record some video sessions as I retire them to the archives.

First big announcement.

All future demo downloads will be on Github!

The download files for my demos on Upskill Your Javascript - from building JS WebParts for SharePoint to Office Add-ins and Azure Functions is up first.

https://github.com/johnnliu/demo-upgrade-your-js

The main driver for this is that the files are updated overtime, and Github really provides a much better place for me to point people to and say the latest files are over here.  Check it out, and if you have Issues - tag them directly on the lines.

So that's the first of the big news.

Seems obvious now...

Seems obvious now...

 

 

Azure Functions, JS and App-Only Updates to SharePoint Online

Have you ever, really wanted to have your JavaScript perform a RunWithElevatedPrivileges against SharePoint Online?  Do something that the current user just don't have permission to do?

Today we tackle this core problem that every SharePoint JavaScript developer has thought about at least once.  And we will do it with AzureFunctions.

1.  Register Azure Active Directory App

You can go through dev.office.com to get started - but you'll need various bits of the portal.  So do this from old portal https://manage.windowsazure.com

You need to write copy out your Client ID and Client Secret

Lets give this some App-Only Permissions

Also need to allow implicit flow, that means download the manifest, change the json (below) and upload it back.

 

2.  Create Azure Function

Go to http://functions.azure.com and sign in. 

This picture looks like an ad in my blog...

This picture looks like an ad in my blog...

Create a new function - I choose the HttpTrigger with Node template.

2.1 Add NPM modules

NodeJS modules (= C# reference libraries) are loaded via npm install (= nuget).  To access this, you need to go to

Function app settings > Go to App Settings > Tools(tipped by @crandycodes)

npm install adal-node
npm install request

The errors are because I don't have a package.json file in the folder.  Add adal-node and also request.  Check they are here.

2.2 NodeJS code

Go back to the function and add this code

var request = require("request");
var adal = require("adal-node");

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    var authorityHostUrl = 'https://login.microsoftonline.com';
    var tenant = 'johnliu365.onmicrosoft.com';
    var authorityUrl = authorityHostUrl + '/' + tenant;
    var clientId = '37ded58a-YOUR-CLIENT-ID';
    var clientSecret = 'fE4kulPjYOUR-CLIENT-SECRET=';
    var resource = 'https://graph.microsoft.com';

    var authContext = new adal.AuthenticationContext(authorityUrl);

    authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, tokenResponse) {
        if (err) {
            context.log('well that didn\'t work: ' + err.stack);
            context.done();
            return;
        }
        context.log(tokenResponse);

        var accesstoken = tokenResponse.accessToken;
        var options = { 
            method: 'GET', 
            uri: "https://graph.microsoft.com/beta/groups", 
            headers: { 
                'Accept': 'application/json;odata.metadata=full',
                'Authorization': 'Bearer ' + accesstoken
            }
        };

        context.log(options);
        request(options, function(error, res, body){
            context.log(error);
            context.log(body);
            context.res = { body: body || '' };
            context.done();
        });
    });
};

Run it

Got our auth token, and got our list of groups.  Perfect.

2.3 Call SharePoint Online

var request = require("request");
var adal = require("adal-node")

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    var authorityHostUrl = 'https://login.microsoftonline.com';
    var tenant = 'johnliu365.onmicrosoft.com';
    var authorityUrl = authorityHostUrl + '/' + tenant;
    var clientId = '37ded58a-YOUR-CLIENT-ID';
    var clientSecret = 'fE4kulYOUR-CLIENT-SECRET=';
    //var resource = 'https://graph.microsoft.com';
    var resource = 'https://johnliu365.sharepoint.com';

    var authContext = new adal.AuthenticationContext(authorityUrl);

    authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, tokenResponse) {
        if (err) {
            context.log('well that didn\'t work: ' + err.stack);
            context.done();
            return;
        }
        context.log(tokenResponse);

        var accesstoken = tokenResponse.accessToken;
        /*
        var options = { 
            method: 'GET', 
            uri: "https://graph.microsoft.com/beta/groups", 
            headers: { 
                'Accept': 'application/json;odata.metadata=full',
                'Authorization': 'Bearer ' + accesstoken
            }
        };
        */
        var options = {
            method: "POST",
            uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('Poked')/items",
            body: JSON.stringify({ '__metadata': { 'type': 'SP.Data.PokedListItem' }, 'Title': 'Test ' + (req.body.name || "hello!") }),
            headers: {
                'Authorization': 'Bearer ' + accesstoken, 
                'Accept': 'application/json; odata=verbose',
                'Content-Type': 'application/json; odata=verbose'
            }
        };


        context.log(options);
        request(options, function(error, res, body){
            context.log(error);
            context.log(body);
            context.res = { body: body || '' };
            context.done();
        });
    });
};

Changed resource to sharepoint.com, also changed the POST options - I want to insert a list item.

This is so sad!  "Unsupported app only token."  :-( :-(

1.1 Backtrack

To move on, we need to backtrack a bit.  @richdizz has a great blog post that documents this - to perform App Only operations on SharePoint Online, the client ID / Client Secret doesn't cut it.  You need to get auth token via certificate.

https://blogs.msdn.microsoft.com/richard_dizeregas_blog/2015/05/03/performing-app-only-operations-on-sharepoint-online-through-azure-ad/

Rich also pointed me to Travis Tidwell (Form.IO) 's excellent

https://github.com/formio/keycred

npm install -g keycred
keycred
(go through the options)

You need three things:

1. The generated keyCredentials JSON

You need to add this part to your AD App Manifest json file - under keyCredentials.  Your JSON should have a really long value for "value"

2. The private key

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAveX4jNn/eBPM1kdRNMPAlh6rT/JFoah9QkUbeYPkYGqWvn7X
~~snipped~~
0ibMc7T5K7AGVT0q0ppBLheFQkeSnPbHJrX40xILEkzd/0RLvC8X
-----END RSA PRIVATE KEY-----

Copy the entire thing, including -----BEGIN and END ----- and save this into a file.  I called mine funky.pem
Upload the funky.pem file into the folder.  I use the VS Online tool.
 

 

3. Certificate Fingerprint:

85b82741408c6c3af462b3a378e3e8963efaad70

2.4 Update to acquireTokenWithClientCertificate

var request = require("request");
var adal = require("adal-node");
var fs = require("fs");

module.exports = function(context, req) {
    context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl);

    var authorityHostUrl = 'https://login.microsoftonline.com';
    var tenant = 'johnliu365.onmicrosoft.com';
    var authorityUrl = authorityHostUrl + '/' + tenant;
    var clientId = '37ded58a-YOUR-CLIENT-ID';
    var clientSecret = 'DONT NEED USE CERT';
    //var resource = 'https://graph.microsoft.com';
    var resource = 'https://johnliu365.sharepoint.com';

    var thumbprint = '85b82741408c6c3af462b3a378e3e8963efaad70';
    var certificate = fs.readFileSync(__dirname + '/funky.pem', { encoding : 'utf8'});

    var authContext = new adal.AuthenticationContext(authorityUrl);

    authContext.acquireTokenWithClientCertificate(resource, clientId, certificate, thumbprint, function(err, tokenResponse) {
    //authContext.acquireTokenWithClientCredentials(resource, clientId, clientSecret, function(err, tokenResponse) {
        if (err) {
            context.log('well that didn\'t work: ' + err.stack);
            context.done();
            return;
        }
        context.log(tokenResponse);

        var accesstoken = tokenResponse.accessToken;
        /*
        var options = { 
            method: 'GET', 
            uri: "https://graph.microsoft.com/beta/groups", 
            headers: { 
                'Accept': 'application/json;odata.metadata=full',
                'Authorization': 'Bearer ' + accesstoken
            }
        };
        */
        var options = {
            method: "POST",
            uri: "https://johnliu365.sharepoint.com/_api/web/lists/getbytitle('Poked')/items",
            body: JSON.stringify({ '__metadata': { 'type': 'SP.Data.PokedListItem' }, 'Title': 'Test ' + (req.body.name || "hello!") }),
            headers: {
                'Authorization': 'Bearer ' + accesstoken, 
                'Accept': 'application/json; odata=verbose',
                'Content-Type': 'application/json; odata=verbose'
            }
        };


        context.log(options);
        request(options, function(error, res, body){
            context.log(error);
            context.log(body);
            context.res = { body: body || '' };
            context.done();
        });
    });
};

Use fs.readFileSync to read funky.pem file and acquireTokenWithClientCertificate instead of ClientSecret

Result

Note - updated by "funky"

Triggers

There are several Azure Functions triggers that immediately jumps out at me as being really useful.

HTTP Trigger

Your JavaScript can call the azure function and trigger the action.  You will need to turn on CORS.  And filter to only your domain.

But this will give your client side javascript ability to call an elevated function.

Timer Jobs

You can trigger your function based on a timer.  In this scenario, your javascript will run against SharePoint like a timer job. 

WebHooks

Currently only Exchange in Office 365 supports WebHooks.  But SharePoint will support it in the future.  When this becomes available, a webhook can trigger an Azure Function and essentially, you have a List Item event receiver.

Except all in JavaScript.

So much fun haha I'm so excited!

Further Reading

 

Hype Level Insane - #FutureOfSharePoint

I wrote this as an email to colleagues.  But it got so long and exciting with pictures I decided to publish it.  There are no spoilers and I don't know any secrets.

 

 

#FutureOfSharePoint event is a keynote follow by sessions

There are more sessions after the keynote by Jeff Teper on whatever they will announce, so if you are a SharePoint guy you might be up for a looooonnnnng day.

Venue setup.  Small invite only place.

 

A lot of SharePoint MVPs have invitations to be physically at the event – there’s a significant gathering of them.  Perhaps Microsoft will consider giving our SharePoint MVP titles back 😉  Office Server and Services is nice.  But being a SharePoint MVP was special.

A group of SharePoint MVPs.  A murder of SharePoint MVPs?

There are times I really think about moving to America.  This is one of those times.

 

Australian Timezone

I’m debating whether to sleep really early and wake up at 2am.  Or just read it tomorrow – I’m pretty sure there will be Sway, liveblog, twitter coverage #FutureofSharePoint, yamjams and a number of MVP impression videos after the event.

I do hope the event will be broadcasted via Skype-broadcast.  Eat your own dogfood Team Office.

Next week Collab365 is broadcasting from MS HQ.  I hope they are setting up with MS and will utilize Skype to run their online conference.  In the past years we have used YouTube.

There is a PowerBI livequery dashboard set up with MS’s new Microsoft Flow

http://whitepages.unlimitedviz.com/2016/05/integrating-microsoft-flow-power-bi-real-time-reporting/

 

SharePoint Can't Be "Dead"

Reading the tea-leaf from what’s public:  To people that says “SharePoint is dead”...

Jeff Teper would not have returned to this role and be “Corporate Vice President of OneDrive and SharePoint” only to tell the world SharePoint is dead.

MS would not invite 20+ SharePoint MVPs to a physical event to tell the world SharePoint is dead.

My expectation is that the news will be really positive.  But my personal hype level is already far too high.

 

Key MS people to follow:

https://twitter.com/jeffteper  Father of SharePoint, Corporate VP.

https://twitter.com/mkashman

https://twitter.com/williambaer

https://twitter.com/AdamHarmetz 

https://twitter.com/danholme   Founder of IT Unity and MVP, recently joining Microsoft to continue Office evangelism. 

 

This rant is already too long and with pictures I might make this a blog post.  But no guesses from me.  Things will either get released with much fanfare, or they won’t.  😊