[O365] Getting started with yo office for hardcore C# devs [part 2]
My previous tech blog covered the use the of the Yeoman generator for creating Office add-ins, specifically add-ins which can be used within Office365 and the Office client applications. This left you with a vanilla sample application which doesn’t do much yet. In this post, I will explain how to implement such an add-in, leveraging the ADAL library for connecting to Office 365 API’s from within your single-page Angular application.
Note: just after releasing this post, a new version of the Yeoman generator was published. Therefore it could be some of the screenshots / samples are not completely accurate. I you run into this, please leave a comment!
Setting the scene
If you’ve watched any of the Microsoft presentations on this topic, you’ll they are high on selling ‘productivity’ within Office. Basically, the idea is that when people don’t have to leave the application they are working in to do some basic task, that’s good for productivity. An appealing example is folks who answer a lot of e-mails, for instance about orders stored in some system. If they can see and use the actual order information inside of Outlook, that might save them some work switching to SAP or whatever application they use, find the order, copy/paste data from it, etc. etc. I think a lot of organizations could benefit from these kind of scenario’s when done right.
For this post, we’ll use an order list stored in SharePoint Online. I would never advise to keep a list of orders in SharePoint by the way, but it’s an easy way to create a compelling example without having to install a fully fledged order processing application.
ADAL.js
ADAL stands for ‘Active Directory Authentication Library’ and that’s exactly what it does; authenticate your code using the Azure Active Directory. Okay, so actually it should have been the AADAL.js library since this won’t work with an on-prem instance of AD. You need this authentication enabled to be able to call any of the Office 365 API’s, otherwise you’ll receive unauthorized responses. The cool thing about this library is that it hooks itself into the the Angular framework and ‘listens’ to any calls it should intercept and augment with the required authentication stuff. A sample can be found in Waldeks github repo: https://github.com/waldekmastykarz/sample-yooffice-cors.
If you haven’t read my previous post, please start there. It covers the basics of getting up and running with the Yeoman generator. For this particular sample, you need to choose the ‘Angular ADAL‘ option when creating your project. This will give you the scaffolding for a project using Angular JS and the ADAL.js library.
After selecting ‘Angular ADAL’, the Yeoman generator will ask you for an application ID.
During the creation of an ADAL enabled app, the generator will ask you to input an application ID. This ID is used by the library to authenticate itself as a trusted application against Azure AD. You need to create an application in Azure AD to get such an ID. Each Office 365 tenant stores data inside of an Azure AD instance, so one is automatically linked to your Office 365 account. To access it, head over to your Office 365 admin center and click the “Azure AD” link which should be located in the bottom of the left-hand pane.
Creating an Azure application + getting client ID
Once in the Azure management portal, select your directory (normally there is one instance listed) and then click “APPLICATIONS“. In the bottom bar, you’ll find the “ADD” button which shows a pop-up asking for some details.
Select the option “Add an application my organization is developing”.
Choose a logical name for your add-in. The “web application” type set by default is the right one since add-ins are web based.
Enter the URL to your application. You can change these later on so during debugging just use ‘https://localhost:8443/appcompose/index.html’ provided you didn’t change it to a different port or URL. It’s important that the URL matches the one that Azure AD will redirect the user to, including /appcompose/index.html. I found that you can get errors otherwise (see debugging post).
Your application should now be created and added to Azure AD. Should it not open by default, click its name to open the details page. Now click “ACCESS WEB APIS IN OTHER APPLICATIONS” to display the Client ID you’ll need. This exposes a GUID string you need to copy.
Now parse this guid into the Yeoman generator and finish the app generation.
Granting permissions to the application
So next to an Office add-in, our application is now also a an “Azure AD Application”. It will be using the client ID above to connect to Office 365 and perform actions. That means that we also need to grant it permissions to do stuff. This is also done within the Azure portal.
With the application still opened, go to the “CONFIGURE” tab. The last section on that page is where you grant the application permissions to access other applications. “Other applications” in this case means SharePoint, Yammer, Exchange, etc. For this sample, we need these permissions because eventually we want to query SharePoint for data.
To keep it simple, I selected:
- “Read and write in all site collections”
- “Have full control over all site collections”
Of course in real life scenario’s you might consider to go a bit more fine-grained.
Finishing ADAL.js configuration
After creating your project, open up app.config.js. You’ll see a constant ‘appId’ with your application ID from Azure ID configured. Should you for some reason ever change the application / application ID, you need to update this one too. No need to change anything right now.
In app.adalconfig.js, you’ll find an Adal configuration setting ‘endpoints’. These are the endpoints that the ADAL library monitors. By default, only graph.microsoft.com is present. This means that when you want to query any Office365 API not living on that endpoint, you need to add it here. In this case, add: ‘https://[tenant-name].sharepoint.com‘ (insert your tenant name) so that ADAL will start listening to calls being made to the SharePoint API’s. When you have left this out, the API’s will return a 401 Unauthorized repsonse. Should you want to use any other Office 365 API (Exchange for instance), make sure the URL is configured here!
Creating the dummy order list in SharePoint
For this example, I created a list in SharePoint representing an order list. I added the following fields to the list:
- Order ID (Title field renamed)
- Customer name (text)
- Order Date (datetime)
- Order Amount (currency)
- Order Status (choice)
Of course you can use other fields when you like to, this is just meant as an example. Add a couple of items to the list so there is something to show. The end result should look something like:
Implementing data service
Ok, you’ve now got everything set-up to actually start getting and displaying data. The project scaffolding implements a data service which you can leverage for this purpose. By default this service only returns a dummy object, we’ll be replacing that code by our call into SharePoint to get orders from the order list.
Open up data.service.js and replace the code by the code below:
(function(){ 'use strict'; angular.module('officeAddin') .service('dataService', ['$q', '$http', dataService]); /** * Custom Angular service. */ function dataService($q, $http){ // public signature of the service return { getData: getData }; /** *********************************************************** */ function getData(){ var deferred = $q.defer(); var endpoint = 'https://sclatos.sharepoint.com/sites/jsiegmund/_api/web/lists/getByTitle(\'Orders\')/items'; // issue query for all sharepoint lists in specified site // // NOTE: because this is a cross domain REST call, the browser will first issue an HTTP OPTIONS request to check // that the REST API supports CORS requests for specific HTTP methods $http.get(endpoint) .then(function success(response) { var dataObject = { items: response.data.value }; deferred.resolve(dataObject); }, function error(response) { deferred.reject(response); }); return deferred.promise; } } })();
I’ve added the $http object to the function parameters and inject it on line 5. In Angular, the $http object is used to perform GET and POST calls. Remember that ADAL will intercept these calls and handle the necessary authentication.
The data is queried using the SharePoint REST API’s. For more information on these, check out this page.
The dataObject is already mapped to the viewmodel, this is done in home.controller.js. What we do need to do is add some HTML to bind the data inside of the view, this will display it on the page. To do so, edit home.html in the home folder, and replace the contents by:
<div data-ng-repeat="item in ::vm.dataObject.items"> Order ID: {{item.Title}}<br/> Customer: {{item.Customer_x0020_name}}<br/> Amount: {{item.Order_x0020_amount}}<br/> Date: {{item.Order_x0020_date}}<br/> Status: {{item.Order_x0020_status}} </div>
The above HTML snippet will now be repeated for every object in the items collection, which are the items retrieved by the REST call. And when all is well, you should now see a list of order data inside of your Angular app!
Adding some Office UI Fabric styling magic
Although you as a developer might be super thrilled about what you see on your screen, anyone who is not a developer will say “So you have some text on your screen, congratz”. To make a real impression, let’s make it look good too.
To make add-ins blend-in with the Office applications themselves, Microsoft provides us with the Office UI Fabric. Simply put, this is a CSS library you can use to automatically apply Office styling to it so it looks and feels like part of Office. Next to that it also contains controls and stuff, but that’s outside the scope of this blog post.
To style the list above, we only need to add some CSS classes to the HTML. First, replace the HTML in the body of index.html with:
<div id="header"> <div class="ms-Grid"> <div class="ms-Grid-row"> <div class="ms-Grid-col ms-u-sm8 ms-u-md8 ms-u-lg8" > <div class="ms-font-xl" style="line-height:2em">Order list</div></div> </div> </div> </div> <div id="content"> <div> <div id="container" ng-app="officeAddin"> <div data-ng-view></div> </div> </div> </div>
And in the head tag, add a short CSS snippet (eventually this should be in a seperate file):
<style type="text/css"> #header { top: 0; left: 0; width: 100%; height: 50px; /* Fixed header height */ padding: 5px; background-color: rgb(220,220,220); } </style>
This will give you a styled header and wrapper for the content (which is optional). Now open up the home.html view file and replace its contents with:
<div ng-hide="::vm.dataObject.items"> Loading list of order items... </div> <ul class="ms-List"> <div data-ng-repeat="item in ::vm.dataObject.items" class="ms-ListItem"> <span class="ms-ListItem-primaryText">Order #{{item.Title}}</span> <span class="ms-ListItem-tertiaryText">Customer: {{item.Customer_x0020_name}}</span> <span class="ms-ListItem-tertiaryText">Amount: {{item.Order_x0020_amount | currency:"€":2}}</span> <span class="ms-ListItem-tertiaryText">Date: {{item.Order_x0020_date | date:'MM/dd/yyyy @ h:mma' }}</span> <span class="ms-ListItem-tertiaryText">Status: {{item.Order_x0020_status}}</span> <i class="ms-Icon ms-Icon--caretLeft"></i><a href="" ng-click="::vm.insertOrder(item);">Insert into e-mail</a> </div> </ul>
This will show a loading text whilst the add-in is loading data. All of the elements will be styling using the Office UI Fabric classes. The end result should display like this:
I’m no UI specialist, so I admit there is room for improvement. But it’s already a lot better than what you saw above (imho…). One last bit left: implementing the “Insert into e-mail link”.
Interacting with the e-mail message
Eventually you want your Outlook add-in to interact with the e-mail message your composing. The loaded Office.js file provides the API’s requires to do so. In this case, we would like to insert the order data into the e-mail. For this to work, I implemented an insertOrder method in the controller:
vm.insertOrder = function(order) { var content = formatHtml(order); console.debug('Inserting into e-mail: ' + content); Office.context.mailbox.item.body.setSelectedDataAsync(content, { coercionType: Office.MailboxEnums.BodyType.Html }); };
As you can see, writing to the body of the e-mail message is as simple as a single call to setSelectedDataAsync, which will paste the string into the e-mail message where the cursor is (or replace selected text). Note that you need to pass in the coercionType option to tell the API you’re sending HTML its way.
The method is called when the user clicks on the link, this is done by using ng-click to let Angular bind the function to the click event, passing in the item that was clicked.
As a helper method, the formatHtml method takes care of converting the order object into a string of HTML:
function formatHtml(order) { var result = "<br/><div>"; result += "<div><strong>Order #</strong>" + order.Title + "</div>"; result += "<div><strong>Customer: </strong>" + order.Customer_x0020_name + "</div>"; result += "<div><strong>Date: </strong>" + order.Order_x0020_date + "</div>"; result += "<div><strong>Amount: </strong>" + order.Order_x0020_amount + "</div>"; result += "<div><strong>Status: </strong> " + order.Order_x0020_status + "</div>"; result += "</div>"; return result; }
That’s nice and simple, right? Now clicking on one or more of the orders will send the text to the e-mail body:
Back to our original scenario. Imagine someone who has a lot of contact with customers concerning orders. Customers send e-mails with questions about certain orders. Using the order add-in, this user can now simply select the order number in the e-mail, query the order application and quickly see & use the basic details of this particular order. No switching or copy/pasting required any more!
When things go wrong
During my add-in adventures I encountered quit some things that might put you off. So I decided on doing a seperate post on debugging Office Add-ins, read that one here.
Conclusion
In this two-post series I showed you how to get started with creation of an Office add-in for Outlook. A couple of things about this concept are important to keep in mind:
- Exactly the same model works for Word, Excel, PowerPoint, etc. The only real difference are the API’s to interact with an e-mail, document, spreadsheet… you get the idea.
- Where I now fetched data from a custom SharePoint list, you can use any data source / API that’s available to your JavaScript. Important is to verify the data source supports your call, implementing CORS (see debugging post)
- Your add-in is just HTML + JavaScript. This means you can pretty much do anything that a webapplication can do. I would not recommend writing an entire application as an add-in though, rather see it as an extension for an existing (or new) web application to increase productivity for those people doing a lot of daily work inside of Office applications.
Any comments or questions? Feel free to use the comments section below!
November 16, 2015 at 2:52 pm |
[…] my previous posts I wrote about creating an office add-in using the Yeoman generator and my first add-in using Yo Office. Now as long as everything just works, these samples are doable. But when things start to go wrong, […]
November 17, 2015 at 11:05 am |
[…] course your add-in is still empty since you’ve only got scaffolding now. Head over to http://blog.repsaj.nl/index.php/2015/11/o365-my-first-add-in-using-yo-office/ for part 2 of this blog on how to implement an […]