Update on SharePoint and Office Development - 2016 Feb edition

We rotate through different topics and presenters in the Sydney SharePoint user group.  But I finally got my turn to present a developer topic, and I wanted to do a quick primer on all things Office Developer related.

This turned out to be a REALLY complex talk.  I wanted to cover ALL the cool new stuff in Office Dev.  But as it turns out every month (and now, every week) new things come out. 

I ended up with:

  • Brief Introduction to dev.office.com
  • Brief Introduction to PnP
  • Where we have been with SharePoint Add-Ins
  • Where we are going with Office Addins: 
  • NEW API: Microsoft Graph API
  • NEW AUTH: OAuth 2.0 and ADAL(js)
  • NEW TOOLS: New tools with Node, NPM, and Yeoman Generator (YO OFFICE)
  • Build a demo Office Addin that talks to both SharePoint Online and Microsoft Graph
  • Run on web and desktop

The presentation clocked in at 1 hour and 20mins - I started a bit earlier.  And kind of flopped off at the end as I run out of steam after the demo :-O

Then we gave out all the swag from dev.office.com so all is happy faces.

The PowerPoint presentation is here:

2015 February Presentation - Update on SharePoint and Office Development

 

Demo Fail

So at the end, the Office Add-In didn't load from the App Catalog on my desktop.  I went home and got it to work, here are screenshots to proof it.

Oh NO - no apps

Add SharePoint App catalog to trust center

 

Here it is.  The same addin working on desktop - talking to Microsoft Graph and showing Group Conversations

 

Convert SharePoint JSOM's ExecuteQueryAsync to Promise in the Prototype

Today's blog is about adding an additional method to SharePoint JavaScript Object Model (JSOM)'s ClientContext object, so we can use it directly like a promise.

I call it "executeQuery" (instead of executeQueryAsync)

Wrapper with jQuery's $.Deferred

SP.ClientContext.prototype.executeQuery = function() {
   var deferred = $.Deferred();
   this.executeQueryAsync(
       function(){ deferred.resolve(arguments); },
       function(){ deferred.reject(arguments); }
   );
   return deferred.promise();
};

Wrapper with AngularJS's $q

SP.ClientContext.prototype.executeQuery = function() {
   var deferred = $q.defer();
   this.executeQueryAsync(
       function(){ deferred.resolve(arguments); },
       function(){ deferred.reject(arguments); }
   );
   return deferred.promise;
};

How do you use this?

var ctx = SP.ClientContext.get_current();
var web = ctx.get_web();
ctx.load(web);
var promise = ctx.executeQuery(); // look!  :-)

promise.done(function(){
  console.log(web.get_title());
});
promise.then(function(sArgs){
  //sArgs[0] == success callback sender
  //sArgs[1] == success callback args
}, function(fArgs){
  //fArgs[0] == fail callback sender
  //fArgs[1] == fail callback args.
  //in JSOM the callback args aren't used much - 
  //the only useful one is probably the get_message() 
  //on the fail callback
  var failmessage = fArgs[1].get_message();
});

This may seem to be just small syntactic sugar, but now you have JSOM returning a promise that you can chain, loop, combine and juggle to your heart's content!

Remember, jQuery.ajax, SPServices, AngularJS, and now JSOM all returns promise objects now.

Thinking with JS Promise and Promises

 

Here's a real life example of a quick design iteration that we went through with promises this week.

In AngularJS (but this applies to any JavaScript), we have a function that calls the server to do a long running process.

function longProcess() {
    // dataservice returns a promise and we return that to our caller.
    return dataservice.longservice();
}

As this could potentially take nearly a minute, we want to add a waiting dialog.

AngularJS UI provides a modal dialog service $modal

function longProcess() {
  // create dialog
  var dialog = $modal.open({ templateUrl: 'wait.html' });

  var promise = dataservice.longservice();

  promise.then(
    function(){
      // success
      dialog.close();
    },
    function() {
      // fail
      dialog.close();
    }
  );
  return promise;
}

 

This works great.
But sometimes, the dialog flashes past too fast!  While the user is still trying to read it, it disappears.

Before we run off into code, let's stop a do a bit of thinking.

What we need is to combine the original promise, with a second new timer promise (of say 5seconds).  When both the service call has finished and the timer is up, we will resolve the promises.

So in pseudo code, it'd look like:

var p1 = service();
var p2 = $q.timer(5000);
var all = $q.all([p1,p2]);
all.then( /* resolve */ );

Note: $q is AngularJS' lightweight implementation of a Promise/A library Q.  The pseudo-code assumption was that $q would provide a timer-based promise that detonates once time is up.  In reality, $q doesn't provide such a method.  But the $timeout service in AngularJS returns a promise for exactly this scenario.

 

function longProcess() {
  var dialog = $modal.open({ templateUrl: 'wait.html' });

  var p1 = dataservice.longservice();
  var p2 = $timeout(angular.noop, 5000);
  var arrayOfPromises = [p1, p2];
  var promises = $q.all(arrayOfPromises);
  promises.then(
    function(){
      // success
      dialog.close();
    },
    function() {
      // fail
      dialog.close();
    }
  );

  // returns a combined promise to our caller.
  return promises;
}

 

Summary

The dialog opens and shows wait.html - it closes when the service is complete and when the timer has hit 5 seconds.  

 

 

Racing to the Races - Putting our Office App out there

As I'm posting this blog entry, our (SharePoint Gurus) first Office App (Add-In) would be available on the store.

I might let you in on a secret - it has in fact been in the store in the last few days, but as it is our company's first Add-In, we had some hiccups and had to push out subsequent updates.  We are pleased with this version and we'll run with it to the actual Melbourne Cup race, which actually isn't all that far away.  It would be on November 3, 2015, and the horses list would be available on October 31 - a Saturday, yes that means our Add-In would prompt you to automatically update data on Monday morning.

Feedback through the week from our clients has been very supportive.  This could turn out great (or a great learning experience).  But either way, we have fun and we hope our clients and friends have fun with our App too.

The Team

We are all consultants and this Add-In is something we wanted to build for a long time, but never could tear ourselves away from our great clients to just stop and write this Add-In.

  • We learn AngularJS along the way
  • We became pros at JavaScript Promises... chaining promises, grouping promises, catching error promises and retrying them.
  • Everyone in the company got involved.  We are not a large company, but this one Add-In has 100% contribution from the entire team.
  • We had different people deploying to their own developer sites, both On-Premises and Office 365. 
  • We use TFS but had an open checkout policy (you have to merge any changes).  This turned out not as disastrous as we think, it gave us freedom to work on the project when we can, without having to wait for a certain colleague to check in first.
  • We started the journey a long time ago with Wiki pages, Task lists and Yammer discussion group.  We are now on Office 365 OneNote (available anywhere, offline, and synchronized) and Office 365 Groups for conversation.  We use the Outlook Groups app when we are on the run.  If Office 365 Planner had been available, I'm sure we would be all over it too.  We had a white-board with moving tasks and Post-It notes.

The Stack

The Add-In is a SharePoint-Hosted App. 

Sweepstake Horse says SAAI.  Also, horse is sorry he didn't say on-premises

Sweepstake Horse says SAAI.  Also, horse is sorry he didn't say on-premises


The Learning (so far)

  • v2 will be provider hosted.  The complexity would lie in provisioning, and also not all our consultants are fluent with ASPNET MVC or C#
  • The benefits are to do with ease of updating the various components, and hiding core logic.
  • We may tackle NodeJS instead

You

You should download our Add-In and give it a whirl.  Come next Monday, hopefully we hear good things from you.

We already have people asking to do a Rugby World Cup one next year, which would have been fun, this weekend is finals between Australia vs. New Zealand.

AngularJS - Logging Client Side Stack Trace

Nothing beats mythical errors that happens on the client side, but not on your development machine.

This post follows yesterday's post on configuring AngularJS to catch and log exceptions back to the server via an AJAX request.

Logging the Exception

Quick recap of the logging error handler.

function error(message, data, title) {
  var $http = angular.injector(['ng']).get('$http');

  data['url'] = document.location.href;

  var request = {
    method : 'POST',
    url: _spPageContextInfo.webServerRelativeUrl + '/_layouts/MyService.svc/LogMessage',
    data: {
      'log': message,
      'meta': angular.toJson(data),
      'evt': 'error'
    },
    dataType: "json",
    headers: {
      "Content-Type": "application/json; charset=utf-8"
    }
  };
  var promise = $http(request);            

  promise['catch'](function(){
    // send to console log if can't log to webservice
    $log.error('Error: ' + message, data);
  });
}
{
  "exception":
  {
    "message":"[NG-Modular Error] Unable to get property 'Client' of undefined or null reference",
    "description":"Unable to get property 'Client' of undefined or null reference",
    "number":-2146823281
  },
  "url":"http://server/_layouts/MY/form.aspx?IsDlg=1&id=3#/client"
}

Yay it's logging.  But actually, it's pretty hard to figure out where the error is coming from.  "What we need", says the developer to the tester, "is the stack trace.  If we have the stack trace we'd be able to repo this and fix it."

stacktraceJS

Grab: https://github.com/stacktracejs/stacktrace.js/blob/stable/stacktrace.js

function error(message, data, title) {
  var $http = angular.injector(['ng']).get('$http');
  
  // work out the stacktrace
  var stack = printStackTrace({e: data, guess: true});
  data['stack'] = stack;
  // attach to the logged data

  data['url'] = document.location.href;

  var request = {
    //...
  };
  var promise = $http(request);            
}

The following JSON gets logged.

{
  "exception":
  {
    "message":"[NG-Modular Error] Unable to get property 'exist' of undefined or null reference",
    "description":"Unable to get property 'exist' of undefined or null reference",
    "number":-2146823281
  },
  "url":"http://server/_layouts/MY/form.aspx?IsDlg=1&id=138#/",
  "stack":
  [
    "{anonymous}(#object,\"other\")",
    "printStackTrace(#object)",
    "error(\"[NG-Modular Error] Unable to get property 'exist' of undefined or null reference\",#object)",
    "{anonymous}(?)",
    "{anonymous}(#function)",
    "{anonymous}(#object)",
    "{anonymous}(#object)",
    "{anonymous}(?)"
  ]
} 

Tweaks

"Those #function and #object, they look pretty stupid." commented the developer, who now has the stacktrace, but still thinks it doesn't help all that much.

A few hacks to stacktrace.js

if (arg.constructor === Object) {
  //result[i] = '#object';
  result[i] = arg.constructor.toString();
}
...
else {
   //result[i] = '?';
   result[i] = arg.constructor.toString();
}

This gives more readable stacktrace:

{
  "exception":
  {
    "message":"[NG-Modular Error] Unable to get property 'exist' of undefined or null reference",
    "description":"Unable to get property 'exist' of undefined or null reference",
    "number":-2146823281
  },
  "stack":
  [
    "{anonymous}(\nfunction Object() {\n    [native code]\n}\n,\"other\")",
    "printStackTrace(\nfunction Object() {\n    [native code]\n}\n)",
    "error(\"[NG-Modular Error] Unable to get property 'exist' of undefined or null reference\",\nfunction Object() {\n    [native code]\n}\n)",
    "{anonymous}(TypeError)",
    "{anonymous}(#function)",
    "{anonymous}(\nfunction Object() {\n    [native code]\n}\n)",
    "{anonymous}(\nfunction Object() {\n    [native code]\n}\n)",
    "{anonymous}([object Event])"
  ],
  "url":"http://server/_layouts/MY/form.aspx?IsDlg=1&id=138#/"
}

 

Future of stacktraceJS

The latest version of StacktraceJS (as of August 2015) is marching towards a full promise/A pattern where you request the stacktrace, and it returns you a promise.  Then promise resolves to an array of stack frames, which you can then pretty-print to your own liking.

var callback = function(stackframes) {
    var stringifiedStack = stackframes.map(function(sf) { 
        return sf.toString(); 
    }).join('\n'); 
    console.log(stringifiedStack); 
};
var errback = function(err) { console.log(err.message); };
StackTrace.get().then(callback, errback)

Expect the syntax to change, soon.

I'm also assuming for modern browsers, it'll even look into requesting SourceMap files for minified versions of JavaScript/TypeScript and resolve those lines in the stacktrace produced.   Currently though, it doesn't do all those things.  What it does manage so far, cross browser, is already fairly impressive and a great starting point.