A Hybrid Future for On-Premises

Hybrid - In Theory

I think it is no suprise for us watching from the SharePoint world (sometimes with a slight envy) at all the investments in the cloud.

Microsoft makes no secret about this - cloud is a massive growth area and an area that Microsoft is and will aggressively pursue.

SharePoint itself is a product born On-Premise. But many of the Experiences are now born-in-the-Cloud.

What I was very relieved to see though, is that in this mad push for Cloud-First, Microsoft reaffirms that they will not leave their customers behind. This is where I feel the Hybrid story that has came out is so refreshing.

What's coming down?

  • OneDrive for Business coming to SharePoint 2010
  • Delve coming to SharePoint 2013 first
  • Continue to evolve Hybrid Search

Hybrid in the Real World

The landscape "I" see.  This part is where I get yelled at, or perhaps I'm seen as a Fanboy.  I'll just say what I saw.

In the year 2013 - I saw the future that Microsoft wanted was all Cloud.  I was very dismayed - Australia is not particularly fast at going to the cloud.  Many of our enterprises aren't even migrating their SharePoint installations from 2010.  What about data sovereignty?  In the light of NSA spying case in 2014 it looked even worse. 

In the following year June 2014, the Australian Government modified its policy to say it is up to each Department Head to decide whether it is OK to store data offshore.  No doubt pushed by both budget cuts, internal push, external Vendor Pricing and a public statement of cutting out unnecessary Red-Tape.

Now, I hear cloud being implemented left and right.  Prime examples?

  • Exchange Online - much bigger mailboxes than on-prem.  Mobile friendly.
  • To get Exchange Online, a company pretty much has their Active Directory synchronized to Azure AD.  ADFS is nicer for SSO, but more servers.  Small and medium enterprises are pretty happy with DirSync.  That's another tick.
  • OneDrive for Business - relatively large personal storage space that allows Enterprise IT control
  • Office Client Licenses.  As part of the Office 365 package, the cheaper client licenses (and up to 5 devices, as well as additional mobile/tablet licenses) are also a huge win.
  • Yammer - Corporate-friendly, sanctioned "social platform".  Seriously, your youngster employees wants to talk, at least give them the right place to make that conversation heard.
  • Lync/Skype for Business - Lync Online took care of a lot of remote VOIP scenarios.  Lync Server worked well with Polycom and other On-Prem solutions.
  • Extranet Sites (SharePoint) where the company wants to share "some" content with an external partner.

There will always be companies that can't move everything to the cloud, but I think more and more companies are considering what they *could* move.  Most companies don't really want to host their own Exchange Server, unless they really have to.  And even for those rare cases, my Bank client is implementing Yammer as their Enterprise Social solution.

 

Reading the Tea Leaves

Oh my favourite activity.  I love doing this and yet I'm so bad at it.

I'm terrible at reading the future.  So I only wanted to mostly comment on the past.  Perhaps as a consultant that works across many different sectors (building, education, transport, mining and banking), and as a community person that loves to talk to everybody I meet, I do see quite a bit.

And what I see aligns with what Microsoft is doing.  So I think it's safe to predict this one:

Bet on the Cloud.  And if you can't do that yet, Bet on Hybrid.

 

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.

 

InfoPath Javascript - fixing image control tooltip.

In a previous blog post I discussed using Picture Controls to host lots of images within InfoPath and manage the tooltip.

InfoPath FormServer renders picture buttons with a pop-up Picture Icon that will hide any alt-text that you've set on that picture.

The key is in the Core.js file, which you should NOT modify.  As this is what could happen if you modify core javascript files.

function LinkedPicture_OnMouseOver(a, c) {
    ULSrLq:;
    var b = ViewDataNode_GetViewDataNodeFromHtml(a),
        d = PictureControl_GetPrimaryDataNodeValue(b);

    /* This modification hides the picture icon when the control is disabled */
    var e = PictureControl_GetPicture(a);
    if (e && e.alt == "Picture") {
        e.alt = "";  e.title = "";
    }
    if (typeof(a.disabled) != "undefined" && a.disabled) {
        return;
    }
    /* end modification */

    a = PictureControl_EnsureControl(a);
    d != "" && PictureControl_ShowPictureIcon(a, b, true);
    LeafControl_OnMouseOver(a, c);
}
LinkedPicture.OnMouseOver = LinkedPicture_OnMouseOver;

So instead, you should create a separate webpart page, put the Form control on that page, and add this script override for the LinkedPicture_OnMouseOver function. 

The key is "if (a.disabled) return"

Then in your InfoPath form template, set the control to read-only will ensure the Picture icon doesn't appear on hover, allowing the title text on your image to show up on the browser.

 

 

[Meta] Updating johnliu.net from Squarespace v5 to v7

This is actually something really, really long overdue.  v5 is from the year 2008.  That is SEVEN years ago.

You will notice:

  • Responsive Design
  • Comments are replaced with Disqus - the original comments are imported, but Squarespace's own comment spam detection has not been great for me. 
  • Squarespace v5 renders pages in X-COMPAT-IE8 which means it looked extra sucky.

You will find missing:

  • Some of my game blog pages - I've removed them because they are just the rambling of a young man.
  • /Storage is a bit funny - I "hope" I imported my old stuff properly.
  • My badges and tag cloud.  I hope to add them back in time.
  • I noticed the RSS feeds got confused and prepended www.johnliu.net - I've removed the www but there may be duplicate records in the RSS reader.

What I am missing:

  • I am absolutely devastated that v7 doesn't work with Windows Live Writer.