JSDoc – Wow, I just learned something cool

As I start to delve deeper into the world of Office.JS and JavaScript, I am having some fits with how things are done and how things are implemented. Besides getting used to a whole new load of terms, finding out that there are more frameworks than there are developers (ok, maybe that is a small exaggeration), or that JavaScript is not “really” object-oriented (and that’s “ok”) is just nearly too much to handle.

Then there is one of my biggest complains with JavaScript is it use of types. You can get in a lot of trouble here and you can even break JavaScript from its root if you really prototype something wrong (see monkey patching). Additionally, scope can be an issue. And then there is Visual Studio 2015 doing it’s best to try to help you with types, but then it fails you. For example, you type “something(dot)” and you expect IntelliSense to come to the rescue – and low and behold, you get what I like to call: “The yellow bangs of hell.“(tm) wlEmoticon-hotsmile.png

yellow-bangs-of-hell.png

I have been coding with this problem thinking I am all alone in this world screaming every time an object, string, or number fails at IntelliSense. Then on an internal Microsoft email thread where the virtues of TypeScript and JavaScript were being contemplated, a whole world was opened before my eyes. Enter JSDoc. Turns out there is an entire specification around this. And even better – Visual Studio 2015 support it. And it is pretty easy to use. Plus type definition is just the tip of the iceberg, there is a LOT more for me to lesrn. But for now, here is an example of how to define a few common types I frequently use:

/** @type {XMLDocument} */
var xmlDoc = $.parseXML(ewsResult.value);
/** @type {string} */
var theString;
/** @type {number} */
var myNumber;
I have started to use this very recently in my proof of concepts I work on with my customers and I think I just increased my productivity by 50% (or more). Holy cow. wlEmoticon-winkingsmile.png

Great Office.js resource

As you might know (or I hope you do by now), Office Web Add-ins are the new thing. And if you have recently started a project in this new paradigm, you know that it is quite different from Office COM/VSTO/VBA based coding. There is a little bit of a learning curve and some of the new code patterns (namely async) make doing things “the old way” not quite possible. As such this can make getting your head around this programming model a tad tough. And then there is JavaScript too. wlEmoticon-disappointedsmile.png

One of my colleagues, Michael Zlatkovsky (a member of the Office.JS development team at Microsoft) is writing a book: Building Office Add-ins using Office.JS.

book-cover

 

I have had an opportunity to read this book and believe it to be a great resource to understanding the new model. Some of the best parts of the book are where he covers the concepts behind the model, its use of async (and why), how to get around some of the challenges of async with Promises (great chapter), the way to use the Office context objects with some great code patterns, and much, much more.

Michael’s companion website is a must to bookmark as well. Check it out: http://buildingofficeaddins.com.

 

Developing an On-premises Web Add-in

A customer I was working with wanted help developing a fully on-premises Outlook web add-in. By this, they wanted no part of it to reach out to the Internet (Azure or Office 365). They wanted:

  • To connect to their internal Exchange server
  • An internal IIS website
  • And no references to the Internet (including the Office.js).

This is the topology we are trying to achieve:

topology

If you have developed an Office Web Add-in lately, you find it is inherently biased to the Internet. Even the samples and solutions provided assume Office 365/Exchange and Azure websites. In a default, new Visual Studio solution, the links to the Office.js libraries and stylesheets are all pointing to the web. And, so as you might expect, there are some challenges to getting it to work on-premises only.

This posting covers what you must do to get such a solution to work, including getting past some pitfalls.

  1. First, you have to download the Office.js files locally. And especially for Outlook because the Office.js files that are provided by default in your solution folder (“for offline debugging” as part of VS2015U3 or earlier) are missing some features to work with specific builds of Outlook 2013 and Outlook 2016. You will run into some strange “type” missing and “Office not defined” errors if you forget this step.
  2. Once you have downloaded the Office.JS files, you will delete all the files under the Scripts\Office\1 folder and copy in the contents you downloaded in step 1.
  3. Next, find all your HTML pages where you have the following reference:
    <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>;
    
  4. Comment out that line and add the following two lines:
    <!-- <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript></script> -->
    <script src="../../Scripts/Office/MicrosoftAjax.js" type="text/javascript"></script>;
    <script src="../../Scripts/Office/1.1/office.js" type="text/javascript"></script>;
    
  5. Once you have developed your solution, you must setup your IIS server. In general here is what you must do:
    • The IIS Server must have ASP.NET installed, it must have .NET4 installed and you must have the Web Application role enabled.
    • Open IIS Manager
    • Create a site, and figure the folder path
    • Convert the site to an Application
    • Apply an SSL certificate that is already trusted on all your client computers or that has a root certificate authority that is trusted on all your client computers. If you browse to your site using HTTPS and you get a RED warning about an untrusted site, then the certificate is not trusted or properly setup.
  6. Next, and this was a major issue to troubleshoot, your Exchange Web Services certificate cannot be expired. If it is, any EWS call you make will return “succeeded” but will be blank – missing data. Digging into the logs you might find an error: “ErrorInvalidClientAccessTokenRequest” The Microsoft Exchange Server Auth Certificate that is used for OAuth needs to be updated. To do this you have to be logged into the Exchange server as administrator:
    • Run this cmdlet to identify the thumbprint of the certificate being used for OAUTH:Get-AuthConfig | FT currentcertificate*
    • Run this cmdlet to identify the thumbprint of certificates used for other Exchange servers (IIS, SMTP, etc.):Get-ExchangeCertificate | Fl *thumb*
    • Run these cmdlets to configure Exchange to use the valid certificate (copy/paste the thumbprint from the Get-ExchangeCertificate output):$today = Get-Date
      Set-AuthConfig -NewCertificateThumbprint newthumbprint -NewCertificateEffectiveDate $today -Force
    • Run this cmdlet to make sure the changes are published to the environmentSet-AuthConfig -PublishCertificate
    • Run this cmdlet to verify the certificate thumbprintGet-AuthConfig | FT currentcertificate*,previouscert*
  7. Now you can deploy your solution to IIS, update your manifest to point to the IIS server (do not forget the HTTPS) and then install it on the Exchange Server Control Panel (ECP) under the Organization / Add-ins option as a mandatory add-in.
  8. Finally, and this last bit is important: IF USING OUTLOOK 2013 or OUTLOOK 2016, YOU MUST BE LOGGED INTO WINDOWS ON THE SAME DOMAIN AS YOUR EMAIL. I know, I know… for some folks this sucks. I have reported this to our product team and they are looking into it. If you are not logged into the same domain controller as your email address is registered, you will not see the advertised add-in. It will load in Outlook Web Access (OWA), but will not appear in Outlook 2013/2016. The exact cause of this problem is unknown, but hopefully it will be addressed in a future version of the product (Exchange or Outlook or both).

Setting up for 100% on-premises is difficult, but it CAN be done. There are a lot of steps, but if you follow the above prescription, you should get it to work. In time, I hope to see this process get easier. But in an online world where Microsoft Office 365 and Azure are main focus, “old fashioned” on-premises solutions are going to require a little more elbow grease.

NOTE: This entry was contributed to by Arthel Bibbens (MSFT) / Exchange PFE. You can follow his posts on this topic here:

 1-GetOrgConfig Administering Office Add-ins within Exchange 2013 and Exchange 2016

blogs.technet.microsoft.com

I recently worked with a developer to deploy an Office add-in within an Exchange 2013 on-premises environment. This project highlighted a capability of Exchange and Outlook that is a huge shift in the way mail add-ins are developed, deployed, and maintained. Let’s take a look at the key components of Exchange 2013 that support this…

 

Downloading latest Office.js for Local (updated)

I have a customer that does not approve of build add-ins that point to the Internet. They want to develop a JavaScript Add-in for Office, but they cannot have it point to and download files from the Content Delivery Network (CDN). So we are forced to just point to the offline copies of the Office.js libraries. However, the version that ships with the default Visual Studio 2015 Update 3 install is version 1.1.0.9. And because we cannot use the NuGet packager in Visual Studio, from their network (because it was blocked), it forced me to get the files a different way. So, I got to a machine where I could use the command line NuGet tool. I placed this in a folder called C:\apps and opened a command prompt (cmd.exe). From there I typed in the commands:


cd c:\apps
nuget.exe install Microsoft.Office.js

As of this writing (updated on May 8th, 2017) this created a folder in the C:\Apps folder called Microsoft.Office.js.1.1.0.12. That was it. Simple. I then pulled over those files as a zip to the customer project a installed them.

NOTE: The NuGet version of the Office.js libraries are not necessarily the newest. The latest are always available on the CDN, however there is not a way to pull those copies down for a local version. The NuGet version is updated with each point release, but it will lag at some interval behind what is available on CDN.

 

Debugging an Office Web Add-in in Production

It is sometimes tough to determine what is happening in a production environment and you need to get logging information from the add-in to see what is happening. How can you do that?

One way is to build a console.log() option into your add-in that looks for a Debug flag in the manifest. So, you will create two manifest, one that enabled Debugging and another than disables it. More on that in a bit. To start, here is the basic class I created in order to handle this:

/*!
 * logger JavaScript Library v1.0.1
 * http://davecra.com
 *
 * Copyright David E. Craig and other contributors
 * Released under the MIT license
 * https://tldrlegal.com/license/mit-license
 *
 * Date: 2016-08-09T12:00EST
 */
var console = (function () {
    "use strict";

    var console = {};

    console.initialize = function () {
        ///
<summary>
        /// Add a textarea/console to the bottom of the page and then setup the logger
        /// </summary>

        /// <param name="DebugMode" type="Boolean">If debug mode enabled - we show the console for logging</param>
        var debugMode = getParameterByName("Debug") == "true";
        if (debugMode) {
            // add the console to the screen
            $("body").append("<textarea id='log' style='width:100%' cols='2000' rows='7' wrap='off'></textarea>");
            $("body").append("<button id='saveLog'>Copy Log to Clipboard</button>");
            $("#saveLog").click(function () {
                var field = $("#log");
                field.select();
                document.execCommand("copy");
            });
        }

        console.log = function (msg) {
            ///
<summary>
            /// GLOBAL: Logs to the textarea on the page
            /// </summary>

            /// <param name="msg" type="string">The message to log</param>
            if (debugMode) {
                var d = new Date(Date.now());
                var current = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
                var data = $("#log").val();
                $("#log").val(current + " - " + msg + "\r\n" + data);
            }
        };
    }

    function getParameterByName(name) {
        ///
<summary>
        /// Get a parameter form the URL
        /// </summary>

        /// <param name="name" type="String">Name of the parameter to get from the query string</param>
        /// <returns type="String">Value of the paramater</returns>
        var url = window.location.href;
        name = name.replace(/[\[\]]/g, "\\$&");
        var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
            results = regex.exec(url);
        if (!results) return null;
        if (!results[2]) return '';
        return decodeURIComponent(results[2].replace(/\+/g, " "));
    }

    return console;
})();
/*!
* logger JavaScript Library v1.0.1
* http://davecra.com
*
* Copyright David E. Craig and other contributors
* Released under the MIT license
* https://tldrlegal.com/license/mit-license
*
* Date: 2016-08-09T12:00EST
*/
var console = (function () {
"use strict";
var console = {};
console.initialize = function () {
/// <summary>
/// Add a textarea/console to the bottom of the page and then setup the logger
/// </summary>
/// <param name="DebugMode" type="Boolean">If debug mode enabled – we show the console for logging</param>
var debugMode = getParameterByName("Debug") == "true";
if (debugMode) {
// add the console to the screen
$("body").append("<textarea id='log' style='width:100%' cols='2000' rows='7' wrap='off'></textarea>");
$("body").append("<button id='saveLog'>Copy Log to Clipboard</button>");
$("#saveLog").click(function () {
var field = $("#log");
field.select();
document.execCommand("copy");
});
}
console.log = function (msg) {
/// <summary>
/// GLOBAL: Logs to the textarea on the page
/// </summary>
/// <param name="msg" type="string">The message to log</param>
if (debugMode) {
var d = new Date(Date.now());
var current = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
var data = $("#log").val();
$("#log").val(current + " – " + msg + "\r\n" + data);
}
};
}
function getParameterByName(name) {
/// <summary>
/// Get a parameter form the URL
/// </summary>
/// <param name="name" type="String">Name of the parameter to get from the query string</param>
/// <returns type="String">Value of the paramater</returns>
var url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
return console;
})();

view raw
console.js
hosted with ❤ by GitHub

To enable logging you will add the following code to your initialize:


console.initialize();
console.log("Started...");

Once initialized this will add a TEXTAREA to the bottom on the page where the log entries will be loaded. Additionally, it will place a “Copy To Clipboard” button at the bottom that when clicked will copy the contents of the TEXTAREA to the clipboard so that they can be forwarded to you as needed.

Once implemented and initialized, you can add a console.log() anywhere you want in your code to add an entry to the log. Now, how do you turn this on. What this is doing in initialize is to see if the debug flag is set in the Query String of the SourceLocation setting in the Manifest. To turn on debugging, you change the following line as such:


<DesktopSettings>
<SourceLocation DefaultValue="~remoteAppUrl/MessageRead.html?Debug=true"/>
<RequestedHeight>250</RequestedHeight>
</DesktopSettings>

That is it. From this you will be able to share two manifests with your users/administrators. The first one will be your default production manifest and the second one can be loaded if you need debugging information from the add-in.

Office JavaScript API Code Explorers

Recently while preparing an internal Chalk Talk on Office Web Add-in Development, a co-worker presented me with two links I had not seen before and I wanted to share them with everyone:

These Code Explorers are pretty cool in that they contain some common use code patterns that you might find useful in your projects.

excelCodeExplorerCapture

Unfortunately, there does not appear to be one for PowerPoint and or Outlook yet. But the fact they are there for Excel and Word is pretty cool.

EWSEditor

The EWSEditor tool has been around a while and is managed/developed by some of my co-workers that sit on the Outlook Developer Support Team. As I have been developing more and more Office Web Add-ins for Outlook, I have found knowing and using EWS to be a very important skill.

EWSEditor helps in this regard. It is a very powerful, full featured EWS test bed. To get started you download this and extract the contents of the ZIP to a folder. From there you launch it and from the File menu, click Next Exchange Service enter in your email address and then select password and click Ok. Then viola, you are connected:

ewsCapture1.PNG

Once connected, you can start browsing your mailbox using EWS. To get to your Inbox, for example, you select TopOfInformationStore and then select Inbox:

ewsCapture2

From there you can go to different folders and look at the items, properties, and values stored in your mailbox. It is quite handy to understand how these things are structured and stored.

Next, you can click  Tools, EWS Post and test your EWS skills. What I did was entered in my information to connect to my server and filled it in as such:

ewsCapture5

I then entered the following XML:


<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2010_SP2" />
</soap:Header>
<soap:Body>
<m:GetFolder>
<m:FolderShape>
<t:BaseShape>AllProperties</t:BaseShape>
</m:FolderShape>
<m:FolderIds>
<t:DistinguishedFolderId Id='inbox'/>
</m:FolderIds>
</m:GetFolder>
</soap:Body>
</soap:Envelope>

And when I hit run, I got a response you see above. It is that simple. And there are also lots of examples as well. If you click Load Example you will see a lot of XML SOAP requests you can test with:

ewsCapture4.PNG

Download it and give it a try.

 

 

Testing a Web Add-in with Multiple Manifest

First, let’s discuss the architecture and from an Outlook/Exchange perspective:

  1. First, you load your manifest on the Exchange server. The manifest is simply an XML file that contains pointers to your web site (on the IIS server).
  2. When you load Outlook Web Access and click the add-ins button, the Add-ins pane will appear and each application manifest you have loaded will appear in the list.
  3. When you click on one of  the add-is, the task pane will load (in Orange) and your site (located on the IIS Server) will populate in the pane.

The problem arises when you have but ONE Exchange server or ONE developer account for development and test. Developers want to be able to debug the code they are working with which typically loads  from the local instance of IIS Express (localhost). But testers need to be able to work with the latest release build to test.

So, how do you do this?

The key is in the Manifest file. If you open the Manifest file, you will see the <Id> field. This is the most important piece, but there are other areas you should/need to update as well. What you will essentially have is two copies of your manifest file:

The first file will use the default ID provided in the project, it should have a name like Developer Release and it will point to your localhost (this is default with the setup of a new Web Add-in).

<?xml version="1.0" encoding="UTF-8"?>
...
<Id>aaaaaaaa-3c56-4091-951e-18fda9e471d4</Id>
<Version>1.0.0.2</Version>
<ProviderName>David E. Craig</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="Demo Developer Release" />
...
<FormSettings>
<Form xsi:type="ItemEdit">
<DesktopSettings>
<SourceLocation DefaultValue="https://localhost:44303/AppCompose/Home/Home.html"/>
</DesktopSettings>
</Form>
</FormSettings>
...
</OfficeApp>

 

diagram - developer - manifest

The second file will have a different unique ID, a different name (like Tester Release) and it will point to your IIS website.

<?xml version="1.0" encoding="UTF-8"?>
...
<Id>bbbbbbbb-3c56-4091-951e-18fda9e471d4</Id>
<Version>1.0.0.2</Version>
<ProviderName>David E. Craig</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="Demo Tester Release" />
...
<FormSettings>
<Form xsi:type="ItemEdit">
<DesktopSettings>
<SourceLocation DefaultValue="https://TestServer/AppCompose/Home/Home.html"/>
</DesktopSettings>
</Form>
</FormSettings>
...
</OfficeApp>

 

diagram - tester - manifest

The real key is having different ID’s and publishing them separately. In Visual Studio the developers will have everything set as shown above (essentially leave everything as default). When they are ready to drop a build to the testers:

  1. Right-click on the Website project and click Publish. Follow the steps to publish the site to the Testers server.
  2. Right-click on the Manifest project and click Publish. Click the “Package the add-in” button.
  3. In the resulting dialog, enter the URL to the Testers site, This will ONLY update the URL, but not change the ID.
  4. Open the <manifest>.xml file in Notepad and then change the fields as shown above:
    • Modify the ID
    • Change the name so that you can identify which one is which
    • Verify the URL is correct.

At this point you are ready to go. And you can create MULTIPLE versions of your manifest. If for example you need one for Testers, one for Developer and then another for Pilot and yet another for experimental testing (each pointing to different IIS instances, sites or even to the Cloud (Azure). You can create as many manifests as you need this way, have them all show up in the Add-ins task pane allowing the testers/users to select the one they wish to work with.

EWS: Sending Email with Attachment

A requirement in an Outlook Web Add-in I am working on required the ability to send an email to an alias with another email as an attachment. I found this a bit challenging as the makeEwsRequestAsync() function limits us to only a handful of EWS functions (EWS operations that add-ins support). This means I was unable to use a lot of the samples I found on the web. However, with the UpdateItem method, I found a way. wlEmoticon-hotsmile.png

First, let me say, I have been asked – why is easyEWS.js not on GitHub. Well, now it is:

https://github.com/davecra/easyEWS

Additionally, you can now reference the latest and greatest in your project at https://raw.githubusercontent.com/davecra/easyEWS/master/easyEws.js.

Next, so I updated the functionality in easeEws via two function:

  • easyEws.getMailItemMimeContent – we use this function to get the MIME content of a specified mail item by the ID.
  • easyEws.sendPlainTextEmailWithAttachment – this function, although a tad more complicated, will create a very simple plain text email and add an attachment send it and save it to the drafts folder.

Here are the two new updates:

    // PUBLIC: creates a new emails message with a single attachment and sends it
    // RETURNS: 'success' is compelted successfully
    easyEws.sendPlainTextEmailWithAttachment = function (subject, body, to, attachmentName, attachmentMime, successCallback, errorCallback) {
        var soap = '<m:CreateItem MessageDisposition="SendAndSaveCopy">' +
                   '    <m:Items>' +
                   '        <t:Message>' +
                   '            <t:Subject>' + subject + '</t:Subject>' +
                   '            <t:Body BodyType="Text">' + body + '</t:Body>' +
                   '            <t:Attachments>' +
                   '                <t:ItemAttachment>' +
                   '                    <t:Name>' + attachmentName + '</t:Name>' +
                   '                    <t:IsInline>false</t:IsInline>' +
                   '                    <t:Message>' +
                   '                        <t:MimeContent CharacterSet="UTF-8">' + attachmentMime + '</t:MimeContent>' +
                   '                    </t:Message>' +
                   '                </t:ItemAttachment>' +
                   '            </t:Attachments>' +
                   '            <t:ToRecipients><t:Mailbox><t:EmailAddress>' + to + '</t:EmailAddress></t:Mailbox></t:ToRecipients>' +
                   '        </t:Message>' +
                   '    </m:Items>' +
                   '</m:CreateItem>';

        soap = getSoapHeader(soap);

        // make the EWS call
        asyncEws(soap, function (xmlDoc) {
            // Get the required response, and if it's NoError then all has succeeded, so tell the user.
            // Otherwise, tell them what the problem was. (E.G. Recipient email addresses might have been
            // entered incorrectly --- try it and see for yourself what happens!!)
            var result = xmlDoc.getElementsByTagName("ResponseCode")[0].textContent;
            if (result == "NoError") {
                successCallback(result);
            }
            else {
                successCallback("The following error code was recieved: " + result);
            }
        }, function (errorDetails) {
            if (errorCallback != null)
                errorCallback(errorDetails);
        });
    };

    // PUBLIC: gets the mail item as raw MIME data
    // RETURNS: the entire email message as a MIME Base64 string
    easyEws.getMailItemMimeContent = function (mailItemId, successCallback, errorCallback) {
        var soap =
            '<m:GetItem>' +
            '    <m:ItemShape>' +
            '        <t:BaseShape>IdOnly</t:BaseShape>' +
            '        <t:IncludeMimeContent>true</t:IncludeMimeContent>' +
            '    </m:ItemShape>' +
            '    <m:ItemIds>' +
            '        <t:ItemId Id="' + mailItemId + '"/>' +
            '    </m:ItemIds>' +
            '</m:GetItem>';
        soap = getSoapHeader(soap);
        // make the EWS call
        asyncEws(soap, function (xmlDoc) {
            //var content = xmlDoc.getElementsByTagName("MimeContent")[0].textContent;
            successCallback(xmlDoc);
        }, function (errorDetails) {
            if (errorCallback != null)
                errorCallback(errorDetails);
        });
    };
// PUBLIC: creates a new emails message with a single attachment and sends it
// RETURNS: 'success' is compelted successfully
easyEws.sendPlainTextEmailWithAttachment = function (subject, body, to, attachmentName, attachmentMime, successCallback, errorCallback) {
var soap = '<m:CreateItem MessageDisposition="SendAndSaveCopy">' +
' <m:Items>' +
' <t:Message>' +
' <t:Subject>' + subject + '</t:Subject>' +
' <t:Body BodyType="Text">' + body + '</t:Body>' +
' <t:Attachments>' +
' <t:ItemAttachment>' +
' <t:Name>' + attachmentName + '</t:Name>' +
' <t:IsInline>false</t:IsInline>' +
' <t:Message>' +
' <t:MimeContent CharacterSet="UTF-8">' + attachmentMime + '</t:MimeContent>' +
' </t:Message>' +
' </t:ItemAttachment>' +
' </t:Attachments>' +
' <t:ToRecipients><t:Mailbox><t:EmailAddress>' + to + '</t:EmailAddress></t:Mailbox></t:ToRecipients>' +
' </t:Message>' +
' </m:Items>' +
'</m:CreateItem>';
soap = getSoapHeader(soap);
// make the EWS call
asyncEws(soap, function (xmlDoc) {
// Get the required response, and if it's NoError then all has succeeded, so tell the user.
// Otherwise, tell them what the problem was. (E.G. Recipient email addresses might have been
// entered incorrectly — try it and see for yourself what happens!!)
var result = xmlDoc.getElementsByTagName("ResponseCode")[0].textContent;
if (result == "NoError") {
successCallback(result);
}
else {
successCallback("The following error code was recieved: " + result);
}
}, function (errorDetails) {
if (errorCallback != null)
errorCallback(errorDetails);
});
};
// PUBLIC: gets the mail item as raw MIME data
// RETURNS: the entire email message as a MIME Base64 string
easyEws.getMailItemMimeContent = function (mailItemId, successCallback, errorCallback) {
var soap =
'<m:GetItem>' +
' <m:ItemShape>' +
' <t:BaseShape>IdOnly</t:BaseShape>' +
' <t:IncludeMimeContent>true</t:IncludeMimeContent>' +
' </m:ItemShape>' +
' <m:ItemIds>' +
' <t:ItemId Id="' + mailItemId + '"/>' +
' </m:ItemIds>' +
'</m:GetItem>';
soap = getSoapHeader(soap);
// make the EWS call
asyncEws(soap, function (xmlDoc) {
//var content = xmlDoc.getElementsByTagName("MimeContent")[0].textContent;
successCallback(xmlDoc);
}, function (errorDetails) {
if (errorCallback != null)
errorCallback(errorDetails);
});
};

Additionally, you no longer need to call initialize on easyEws. And you can simply add a reference to it in your HTML to always get the latest:

<script src=”https://raw.githubusercontent.com/davecra/easyEWS/master/easyEws.js type=”text/javascript”>script>

Once added, you can use EasyEWS.js on your projects. In my particular project, I used the following code, which does the following:

  • Gets the current mail item (entirely) as a base64 encoded string. This is the full MIME representation of the message.
  • It then creates a new mail item and sends it with a comment from the user to the email address specified with an attachment (which is the base64 string we got in the first step).

This is all done with EWS and because of easyEWS.js, it is done in very few lines of code:

    // Loads the form items to attach to events
    function loadForm() {
        $("#forward-button").click(function () {
            getCurrentMessage();
        });
    }

    // This function handles the click event of the sendNow button.
    // It retrieves the current mail item, so that we can get its itemId property
    // ans also get the MIME content
    // It also retrieves the mailbox, so that we can make an EWS request
    // to get more properties of the item.
    function getCurrentMessage() {
        var item = Office.context.mailbox.item;
        itemId = item.itemId;
        mailbox = Office.context.mailbox;
        try{
            easyEws.getMailItemMimeContent(itemId, sendMessageCallback, showErrorCallback);
        } catch (error) {
            showNotification("Unspecified error.", err.Message);
        }
    }

    // This function is the callback for the getMailItemMimeContent method
    // in the getCurrentMessage function.
    // In brief, it first checks for an error repsonse, but if all is OK
    // t:ItemId element.
    // Recieves: mail message content as a Base64 MIME string
    function sendMessageCallback(content) {
        var toAddress = "bob@contoso.com";
        var comment = $("#forward-comment").val();
        if (comment == null || comment == '') {
            comment = "[user provided no comment]";
        }
        try{
            easyEws.sendPlainTextEmailWithAttachment("Message with Item Attachment",
                                                     comment,
                                                     toAddress,
                                                     "Email Attachment",
                                                     content,
                                                     successCallback,
                                                     showErrorCallback);
        }
        catch (error) {
            showNotification("Unspecified error.", err.Message);
        }
    }

    // This function is the callback for the easyEws sendPlainTextEmailWithAttachment
	// Recieves: a message that the result was successful.
    function successCallback(result) {
        showNotification("Success", result);
    }

    // This function will display errors that occur
    // we use this as a callback for errors in easyEws
    function showErrorCallback(error) {
        showNotification("Error", error);// .error.message);
    }
// Loads the form items to attach to events
function loadForm() {
$("#forward-button").click(function () {
getCurrentMessage();
});
}
// This function handles the click event of the sendNow button.
// It retrieves the current mail item, so that we can get its itemId property
// ans also get the MIME content
// It also retrieves the mailbox, so that we can make an EWS request
// to get more properties of the item.
function getCurrentMessage() {
var item = Office.context.mailbox.item;
itemId = item.itemId;
mailbox = Office.context.mailbox;
try{
easyEws.getMailItemMimeContent(itemId, sendMessageCallback, showErrorCallback);
} catch (error) {
showNotification("Unspecified error.", err.Message);
}
}
// This function is the callback for the getMailItemMimeContent method
// in the getCurrentMessage function.
// In brief, it first checks for an error repsonse, but if all is OK
// t:ItemId element.
// Recieves: mail message content as a Base64 MIME string
function sendMessageCallback(content) {
var toAddress = "bob@contoso.com";
var comment = $("#forward-comment").val();
if (comment == null || comment == '') {
comment = "[user provided no comment]";
}
try{
easyEws.sendPlainTextEmailWithAttachment("Message with Item Attachment",
comment,
toAddress,
"Email Attachment",
content,
successCallback,
showErrorCallback);
}
catch (error) {
showNotification("Unspecified error.", err.Message);
}
}
// This function is the callback for the easyEws sendPlainTextEmailWithAttachment
// Recieves: a message that the result was successful.
function successCallback(result) {
showNotification("Success", result);
}
// This function will display errors that occur
// we use this as a callback for errors in easyEws
function showErrorCallback(error) {
showNotification("Error", error);// .error.message);
}

view raw
sample.js
hosted with ❤ by GitHub

Useful Refresh Command

I recently watched a video by my colleague Michael Zlatkovsky in which he demonstrates the changes to the OfficeJS Libraries. And in this video he proposed a nifty little trick I have been using ever since to refresh the task pane app without having to stop and reload the solution (which saves minutes each time you need to stop debugging). What you do is place a refresh button at the bottom of your task pane HTML, like this:

<id="refresh-button">Refresh</button>

Then you wire it up like this:

$('#refresh-button').click(function () {
    location.reload();
});

Simple eh?! Thanks Michael for the nifty tip! wlEmoticon-hotsmile.png