AngularJS $http and logger Circular Dependency

Error

[$injector:cdep] Circular dependency found: $rootScope <- $http <- logger <- $exceptionHandler <- $rootScope <- $location <- routehelper

TLDR:

If you are looking for RequireJS's easy way to late-bind a dependency, here's how you do it in AngularJS.

var $http = angular.injector(['ng']).get('$http');

Story

I imagine this could be a fairly common scenario.

You implemented AngularJS, with a logger service.

 angular
     .module('blocks.logger')
     .factory('logger', logger);

 logger.$inject = ['$log'];
 function logger($log) {
        var service = {
            error   : error,
            log     : $log.log
        };
        return service;

        /////////////////////

        function error(message, data, title) {
            $log.error('Error: ' + message, data);
        }
}

You want to try to catch errors and log it back to your webservice
So add a REST call.

logger.$inject = ['$log', '$http'];
function logger($log, $http) {
    // snip.

    function error(message, data) {
        $log.error('Error: ' + message, data);

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


Error!

[$injector:cdep] Circular dependency found: $rootScope <- $http <- logger <- $exceptionHandler <- $rootScope <- $location <- routehelper

I actually quite like this error.  It's telling me the dependency tree starting from routehelper and looping around to $rootScope

Bingle will tell you to re-architect your solution so you have proper separation of concerns.  But to be honest, I just want to log an error…

So here's how you do it.

var ng = angular.injector(['ng']);
var $http = ng.get('$http');

Final Code Summary

logger.$inject = ['$log'];
function logger($log) {
    // snip.

    function error(message, data) {
        $log.error('Error: ' + message, data);
        
        // inject $http here.
        var $http = angular.injector(['ng']).get('$http');
        if (!$http) return;
            var request = {
                method : 'POST',
                url: _spPageContextInfo.webServerRelativeUrl + '/_layouts/MyLogService.svc/LogMessage',
                data: {
                    'log': message,
                    'event': 'error',
                    'meta': angular.toJson(data)
                },
                dataType: "json",
                headers: {
                    "Content-Type": "application/json; charset=utf-8"
                }
            };
            $http(request);           
        }
}

PhantomJS and Office 365 Brisbane

I thoroughly enjoyed my trip up to Brisbane.  I got to see some friends, saw my sister and presented PhantomJS with SharePoint and SharePoint Online.

 

The response was tremendous, those that came to my session seems really interested and gave me more ideas for future discussions. 

The presentation and demo are here

The new business case scenarios:

  • Automated Smoke Testing of sites - randomly check pages and DOM exist - "nothing has so far blown up"
  • Smoke Testing of sites with different credentials!
  • Scanning and scrapping text off dynamic (Single Page Application) pages and feed them to SharePoint's Search Indexing Service.  Thus, you can have textual search linking back to dynamic URLs.

I also really enjoyed Elaine's session on SharePoint 2016.


The next Office 365 Saturday in Australia will be in Melbourne.  The registration is up here:

http://www.o365saturdayaustralia.com/melbourne

Moving Everyone and Everything to AngularJS

We are late on the boat.  But I'm describing the journey for us, and we are truly on the boat.

 

So in mid 2014, SharePoint Gurus decided that we'll move everyone to JavaScript.  Traditionally, we are very much an InfoPath, Workflows and Configuration-over-Customization type of Consultancy.

My background is hardcore ASP.NET so I get all the fun stuff of writing Full Trust and Sandbox Solutions.  Custom Web Parts, Feature Receivers and Workflow Activities.  Later, as more work moves to client-side scripting, I would write Full Trust REST service end-points that provide lots of functionality to our InfoPath and Front-End Developers.

As InfoPath goes on a slowly (but surely) spiral of death, we look forward to where we should be investing our company's time and skills.  I was asked to consider and plan out the migration path for the company.

What we looked at:

JQuery

JQuery itself was a good starting point, and possibly the simplest JavaScript library one should start with.  Traditionally, it's used for DOM manipulation, but it's usefulness was extended with jQuery Ajax and jQuery Deferred/Promise.  We use JQuery and the various plugins for JQuery for a lot of things.  Additionally, JQuery works very well with SPServices on SP2007 customizations.

KnockoutJS

KnockoutJS is a DataBinding library.  JQuery is great for creating Read Only, One-Way DOM creation (say, you query an REST end point and want to create a bunch of elements in a table or list).  But JQuery is terrible at keeping track of the UI and the Original JavaScript object for you to post that data back.

KnockoutJS fixes that problem.  The good side is that it's intuitive.  The downside is that because JavaScript properties doesn't have onChanged() method, KnockoutJS creates observable property wrappers instead, and the syntax is not as pretty.

DurandalJS

Since KnockoutJS is only a databinding library, when you want to start looking at creating Single Page Applications, you need more stuff.  You need a Module Loader.  You need a Routing System.  And you need View Composition.

Durandal uses KnockoutJS for data binding, and adds RequireJS for module loading and JQuery for AJAX calls.

Although I loved DurandalJS, ultimately I could not recommend it to the whole company because the creator Rob Eisenberg (@eisenbergeffect) moved to AngularJS (and later to Aurelia.io).  I am keen personally on Aurelia, but it is not ready to introduce to the whole company in 2014.

ASP.NET MVC

I personally LOVE ASP.NET MVC.  But here's the problem - we have developers who are used to ASP.NET WebForms, so for them there are two jumps: first they have to learn ASP.NET MVC and then they still have to learn JavaScript.

JavaScript is required learning.

And ASP.NET MVC needs SharePoint Provider-Hosted apps - we can't, for example, use it with SharePoint 2010 or SharePoint 2007.

We have not turned our backs to ASP.NET MVC forever, it's just for the immediate focus, we are fully committed to client-side (Front-End) development technologies.  A few of our advanced guys will double back and pick up ASP.NET MVC later (or NodeJS).

AngularJS

All choices aside, we decided on pushing forward with AngularJS.  For many in the team, they have experience with JQuery but not DurandalJS nor KnockoutJS - so there's not a lot of wasted learning.

For me, I always compare AngularJS to DurandalJS and the biggest issue I have personally is that AngularJS is...  too magical.  UN-intuitively magical.  There are a lot of very custom syntax that you just have to know.  If you didn't know, you wouldn't know it's available.  But if you are looking at someone else's work, you can generally work backwards and understand Ah that's how it fits.  I wish it was more intuitive.

The biggest advantage for AngularJS is just how much community momentum there is for it.  There are a lot of people that understand it well, and lots of people building applications with it, and discussing it on the Internet - anywhere you look.

Additionally, lots of AngularJS and SharePoint / Office 365 training is available online.  Which means that we don't have to re-invent every wheel.

As a company and for our teams, it is a very safe bet.

There are a few related technology in the stack that we are using:

Bootstrap and Font-Awesome both give the front-end developer two things:  Rapid layout and font-glyphs without mucking around outside of the HTML templates and JavaScript code.

AngularJS + TypeScript

Since AngularJS announced that it will be built in TypeScript, there has been a resurgence of activity on the TypeScript front.

If your team has a lot of C# developers, TypeScript is worth your time to invest into.  It will make your team more productive, and as a team, you will write much better code.

 

What I did NOT look at:

All that said, there's also a lot that's unsaid.  I did not look into Backbone, EmberJS or Telerik KendoUI.  The main reason really is that I was personally very happy with DurandalJS, and if it was up to me personally, I would have chose Aurelia to go next.  But the evaluation we went through is to decide which framework to pursue for the entire company.

I always consider that we should never stop learning.  In the JavaScript space this is even more true.  Just like back in 2008 we decided (in my previous company) that we will pursue JQuery, in 2014 - we made a similar decision to pursue AngularJS.

 

An Army of Front-End Developers

So as a whole, our team is turning from InfoPath designers into Front-End Developers.  We have mastered JQuery and now diving into the fun that is AngularJS Directives.

 

Aim to have An Army of Full-Stack Developers in the nearby future

Moving towards a proper MEAN stack.  There are other components to tackle next:

  • M - MongoDB (a NoSQL document database suitable for storing XML and JSON)
  • E - Express (a lightweight web server, aka IIS)
  • A - AngularJS (front-end)
  • N - NodeJS (run JavaScript on the server, aka C# or .NET Framework)
  • Everything will have to run Provider-Hosted, most likely on Azure

For now - we are Front-End developers and happy with just the UI and storage of the form contents.

  • A - AngularJS
  • S - SharePoint Library (storage of XML and JSON - can be easily loaded via client-side scripting)

Summary

https://twitter.com/alwaysbetonjs

https://twitter.com/alwaysbetonjs


Office 365 Saturday Perth #O365PER summary

On 22nd of May I had the pleasure of presenting O365 Saturday Perth.  In the past years, we have called this SharePoint Saturday.  We re-branded the name and this year had a mix of content from SharePoint On-Premises, SharePoint Online as well as Azure and Office 365.

People from Perth are always early risers - the room was quite packed early in the day.

Here's James Milne presenting the keynote.

I presented Introducing PhantomJS: Headless browser for SharePoint/Online.

Had lots of feedback about what people found it interesting, many people said it has potential for scenarios that they didn't think were previously possible.  I'm very keen to hear more feedback especially for different business cases.

Some of the feedback and demos will be rolled into an updated content over the year, so stay tuned.

Here are the PowerPoint and ZIP files for the presentation.

 

SharePoint Add-in: Accessing Webcam with Only Javascript

This blog post details how to access your Webcam via Javascript through the browser, and upload that content to a SharePoint library.  Then, with an added bonus, set it to be your User Profile Picture.

I have build this User Profile Webcam solution as an Add-in for SharePoint (was App for SharePoint).  This is a SharePoint Hosted app.  All the code runs in the browser and access SharePoint via the SharePoint Online API.

Step 1.  Access your webcam.

Modern Browsers:  Immersive Internet Explorer, Firefox, Chrome and Safari all has ways to access your webcam via

getUserMedia

 

But this doesn't work on IE (Desktop).  So for that we'll add a Polyfill (Flash). 

 

There's a project on github that does this nicely, and I use large chunks of the code from their demo.

https://github.com/addyosmani/getUserMedia.js

<script type="text/javascript" src="../Scripts/html5.js"></script>
<script type="text/javascript" src="../Scripts/getUserMedia.min.js"></script>
<!-- Add your JavaScript to the following file -->
<script type="text/javascript" src="../Scripts/App.js"></script>


<div id="webcam"></div>
<canvas id="canvas" height="240" width="320"></canvas>

<br />
<button class="btn" style="width:140px;" id="takeSnapshot">Take a picture</button>
this.snapshotBtn = document.getElementById('takeSnapshot');
$(this.snapshotBtn).click(this.getSnapshot);

this.getSnapshot = function () {
    // If the current context is WebRTC/getUserMedia (something
    // passed back from the shim to avoid doing further feature
    // detection), we handle getting video/images for our canvas 
    // from our HTML5 <video> element.
    if (App.options.context === 'webrtc') {
        var video = document.getElementsByTagName('video')[0];
        //App.canvas.width = video.videoWidth;
        //App.canvas.height = video.videoHeight;
        App.canvas.getContext('2d').drawImage(video, 0, 0, App.canvas.width, App.canvas.height);

        // Otherwise, if the context is Flash, we ask the shim to
        // directly call window.webcam, where our shim is located
        // and ask it to capture for us.
    } else if (App.options.context === 'flash') {
        window.webcam.capture();
        App.changeFilter();
    }
    else {
        alert('No context was supplied to getSnapshot()');
        return false;
    }
}

The code basically takes either the webrtc object and copy the image to the App canvas.  Or use the flash polyfill and Flash will write the image to the canvas element.

 

Step 2. Binary Format

This part is hairy.  But you are a developer, and dealing with Binary Encoding in JavaScript is what we do.

/*
Now, get canvas and do decoding.
1. canvas can return image data in png, in dataURL format.
2. strip the heading string "data:image/png;base64,"
*/

var ctx = App.canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, App.canvas.width, App.canvas.height);
var dataURL = App.canvas.toDataURL('image/png');
var base64 = dataURL.replace(/^data:image\/png;base64,/, "");

This is base64 dataUrl format.

 

/*
Reading.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#Solution_.232_.E2.80.93_rewriting_atob%28%29_and_btoa%28%29_using_TypedArrays_and_UTF-8

We need to convert DataURL to ByteArray
Mozilla has these two functions.
*/

function b64ToUint6(nChr) {
    return nChr > 64 && nChr < 91 ?
        nChr - 65
      : nChr > 96 && nChr < 123 ?
        nChr - 71
      : nChr > 47 && nChr < 58 ?
        nChr + 4
      : nChr === 43 ?
        62
      : nChr === 47 ?
        63
      :
        0;
}

function base64DecToArr(sBase64, nBlocksSize) {
    var
      sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
      nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2, taBytes = new Uint8Array(nOutLen);

    for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
        nMod4 = nInIdx & 3;
        nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 6 * (3 - nMod4);
        if (nMod4 === 3 || nInLen - nInIdx === 1) {
            for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
                taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
            }
            nUint24 = 0;
        }
    }
    return taBytes;
}

var byteArray = base64DecToArr(base64);

This gives us ByteArray.  We're getting close.

I'd like to tell you that I spend a good 3 evenings working to this point.  Try debugging that code and see if your eyes bleed!

/*

Finally, 

The ByteArray needs to be encoded as a string, and in the POST call, "binaryStringRequestBody" needs to be "true"

https://msdn.microsoft.com/en-us/library/office/dn769086.aspx
http://blogs.msdn.com/b/uksharepoint/archive/2013/04/20/uploading-files-using-the-rest-api-and-client-side-techniques.aspx
http://sharepoint.stackexchange.com/questions/54218/sharepoint-2013-rest-api-upload-image

*/

var binaryString = '';
var len = byteArray.byteLength;
for (var i = 0; i < len; i++) {
    binaryString += String.fromCharCode(byteArray[i])
}

SharePoint API wants data in BinaryString format.  Which is basically each byte of the ByteArray encoded as a concatenated string.

 

 

Step 3.  Upload to SharePoint

/*
Use RequestExecutor to post file back to sharepoint
*/

var appWebUrl = context.get_url();
var requestExecutor = new SP.RequestExecutor(appWebUrl);

var uploadPictureEndPoint = appWebUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/Files/add(url=@TargetFileName,overwrite='true')?" +
    "&@TargetLibrary='" + "Pictures" + "'" +
    "&@TargetFileName='" + _spPageContextInfo.userLoginName + ".png" + "'";

/*
Using RequestExecutor, don't need REQUESTDIGEST
*/
//var digest = $("#__REQUESTDIGEST").val();

requestExecutor.executeAsync({
    url: uploadPictureEndPoint,
    method: "POST",
    headers: {
        "Accept": "application/json;odata=verbose"
    },
    contentType: "application/json;odata=verbose",
    binaryStringRequestBody: true,  // binaryStringRequestBody must be true
    body: binaryString,
    success: function (x, y, z) {
        alert("Success! Your file was uploaded to SharePoint.");
    },
    error: function (x, y, z) {
        alert("Oooooops... it looks like something went wrong uploading your file.");
    }
});

 

Step 4.  Setting User Profile Picture.

 

var appWebUrl = context.get_url();
var requestExecutor = new SP.RequestExecutor(appWebUrl);
var setPictureEndpoint = appWebUrl + "/_api/sp.userprofiles.peoplemanager/setmyprofilepicture";

requestExecutor.executeAsync({
    url: setPictureEndpoint,
    method: "POST",
    headers: {
        "Accept": "application/json;odata=verbose"
    },
    contentType: "application/json;odata=verbose",
    binaryStringRequestBody: true,
    body: App.binary,
    success: function (data) {
        alert('Set My Profile Picture succeeded, it will take a few seconds for the change to be propagated.');
    },
    error: function (error) {
        alert("Oooooops... it looks like something went wrong updating your profile picture (no permission?).");
    }
});

For this API call to /setmyprofilepicture to succeed, your App must have additional permissions.

This will ask the user (or Tenant Administrator) to grant the correct permission.

If you don't grant this permission, the webcam can still save picture to library, but it won't be able to set picture as user profile picture.

Summary

  • Get Webcam via Browser
  • Process canvas data to imageUrl to byteArray to BinaryString
  • Upload to SharePoint Library
  • Set as User Profile Picture
  • Achieve Zoolander face!
  • You can download the App for free from the Office Store to see it in action.  
    Feel free as a developer to hit F12 and step through the code.
  • Leave a comment below and let me know what you think.  For example, I think there's a need for an Outlook Add-in that uses the webcam.  Especially, if it can run on an iPad.