Ok, odd title, I know. For some developers “in the know,” they will understand what I am eluding to. So what does it mean? It is short for Yeoman. In their own words, here is what it does:
Yeoman helps you to kickstart new projects, prescribing best practices and tools to help you stay productive. To do so, we provide a generator ecosystem. A generator is basically a plugin that can be run with the `yo` command to scaffold complete projects or useful parts.
So, by “yo office”, I am telling you that Office Web Add-in templates can be delivered through this cross-platform command line tool. This means you do NOT need Visual Studio (or even Windows) to create a new Office Web Add-in project package. More importantly, these packages are setup through NodeJs Package Manager and are configured to use Node.js web server (which comes on MANY different platforms).
Now, if you are like me (a 20+ year VS Office Developer veteran), when I first heard this, it all sounds just like this:
Γράφω στα ελληνικά και αν καταλαβαίνετε αυτό, καλό για σας
FYI: That is Greek.
So, I have a choice here and that is to make this a long blog post, or a short blog post and I dislike making long posts as much as you do reading them, so… In this case, here is the executive summary: The Microsoft Office Developer team is trying to make Office an accessible development platform no matter where you come from (experience wise) or what platform you use.
Now, for “super hip” developers living on the cusp of command-line based free developer tooling, put this in your MacBook:
yo office
For the rest of you…
To get this to work, you need to install Node JS from here. And next you need to install the prerequisites as per these steps. Once installed, you will open the Node JS command prompt (in Windows, from the Start menu, type NodeJS and it should appear as a choice) and perform these steps:
Change directory (using cd) to the folder where you want the project to live.
Type “yo office” and then answer the questions.
In my case, I developed an Outlook add-in, with a manifest, using JavaScript, called “Outlook-Sample-1” and I chose JQuery as the framework. Here is the output:
When it is done, it will create a folder called Outlook-Sample-1 and place a set of files in it that you can edit and the NodeJS configuration to run it from a standalone web server locally. I used Visual Studio Code (or VS Code, as it is known, which is a new multi-platform code editor from Microsoft that runs on Mac, Windows, and Linux), to edit the add-in.
NOTE: If you have configured everything in Visual Studio Code correctly, you can not only get NodeJS to interact from the “Terminal Window” you can also hook to GitHub to push/pull your projects. It is a whole other topic for a whole other blog post, but VS Code is a bare essentials utilitarian cross-platform code editing development tool.
Now, which files you need to edit and such are beyond the scope here, but if you have done some development in this area before, you can probably quickly figure it out. I might post more on this in the future. Stay tuned!
Ok, ok… I know, I know… I need to stop here and address the big huge hulking elephant in the room. YES, these are command line tools. Yes, DOS is still dead. No, it is. Really. Repeat after me, DOS “IS” DEAD. Now, just accept that this is the way it is, because this is the way it is. I know, it seems so… 1994… Neo called from the Matrix to assure you it is, indeed, 2017. For my fellow Visual Studio, 20+ year developer gurus: “deep breaths.” Now, back to our regularly scheduled broadcast..
Here is a view of my project from VS Code, with the Terminal Window open…
Once I was done, I opened a NodeJS command prompt in the Terminal window and launched my project using the following command:
npm start
Once you run this command, you will effectively start NodeJS web server at http://localhost:3000 which will be hosting your WebAdd-in. But you still need to side load your manifest. To do this, you will need to follow these steps (for Windows), here for Mac, here for Outlook. This gets all kinds of technical and deep into other areas that are potentially future blog posts, but the point is that you can then debug your add-in locally no matter which platform you are on.
Ok, Ok… back to the elephant…
So, is this BETTER than Visual Studio Enterprise and the JavaScript debugger there and code assistance I get?
Well, this is more nuanced… I lean towards “no” as most of this stuff is too lightweight, especially given my experience. Especially without the debugging. But, I do see some advantages (besides the command line tools) that have its advantages for use on Windows:
It is available across multiple platforms – including Windows. VS Code, Node JS, and “yo office” are everywhere, anywhere, over there, under that, etc..
Out-of-the-box support for TypeScript. Which I have not blogged on a lot, but Michael Zlatkovsky has covered well in his book. TypeScript makes JSDoc seem like child’s play.
Out-of-the-box support for the Node Package Manager ecosystem. This is a huge repository of reusable code.
VS Code is a light weight editor that hooks into NPM, Node and Git really well. And while it does not debug, recent builds of Office have added the ability to hook to a debugger right from your taskpane. Which is only good if you have a taskpane, but it is a start.
Node provides auto-refresh (browser-sync), auto-compilation, and other goodness.
Again, this might look like a lot of Greek, but if you find your shop turning Greek, you can at least know some starter phrases to begin acquiring the knowledge you need to speak it.
So, why is any of this important to me? Especially if you are telling me:
“I use Visual Studio Enterprise (or Pro) and I am happy with it, why would you introduce me to this Greek language lesson.”
The answer comes in three points you can take away:
Office development is really multi-platform capable now. This correlates to the fact the Office Applications are also multi-platform. Write your add-in once, and it will run (or the future plan is, it will run) on every platform you can think of: Windows, Mac, iOS, Android, Linux (or essentially an browser or any platform with a browser that has HTML5 support).
You know more about the vision, commitments and goals of the core Microsoft Office teams. If you get nothing out of this other than Microsoft is committed to true multi-platform capabilities with Office Web Add-ins, you have come away with a major point. But there is also the knowledge on how Office development can be done no matter the developer background. And hey. You 20+ year VS Office Developer veterans: take this to heart and read into it, because these are the tea leaves; then look over to your mentee that is likely your kids age and <sigh>.
You can move to another platform and still take what you know with you. This means that “cool” MacBook you bought several years back and gave to your kiddo when they went off to college can actually do something more than read email and browse web pages. Uh, I still strongly suggest you stick to Web Add-in development on Windows and Visual Studio Enterprise if you are already there… just saying…
So, with this, I hope I have sufficiently given you a vision of things to come, and maybe even given a few of you some new things to research and delve into. If you want more, the entire how-to with Yeoman, including a cool video, is on the following page:
If you are an Outlook Web Access developer trying to create an add-in to help mitigate the accidental release of Personally Identifiable Information (PII), you have probably been looking for a way to stop the send of the message if it does not pass a test. The functionality has not been a part of the new OfficeJS object model, until now. However, there are several things you need to know in order to set this up. Here are the general steps:
Use PowerShell to create a policy to enable to the OWAOnSendAddinAllUserPolicy
Use PowerShell to set that policy for a mailbox or group of mailboxes
Update your manifest file to enable the functionality and specify the event function you want to call when the user presses send
Write your JavaScript to properly handle the event.
NOTE: There are several caveats to this functionality. These are: you CANNOT publish an app using this functionality to the Store; this will not work on Shared mailboxes; and this will not work from any other clients, just Outlook Web Access (for now).
Create the OWAOnSendAddinAllUserPolicy
To set the policy for a user account, you need to create a policy with it enabled (or edit the default or specific policy you have applied). First, you need these steps to connect to the Exchange server as an administrator and establish a session:
Once you have done, this you can then start writing the functionality into your code.
Update the Manifest file
The manifest will require a new and additional version overrides node. You will then use the FunctionFile to define an Extension point in your code. This will be a function you call. If you are not familiar with Function Files, here is a link to how to create one:
[code lang=”xml” collapse=”true” title=”click to expand if the embedding below is not visible.”]
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides" xsi:type="VersionOverridesV1_0">
<!– On Send requires VersionOverridesV1_1 –>
<!– https://dev.office.com/docs/add-ins/outlook/outlook-on-send-addins –>
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1" xsi:type="VersionOverridesV1_1">
<Requirements>
<bt:Sets DefaultMinVersion="1.5">
<bt:Set Name="Mailbox" />
</bt:Sets>
</Requirements>
<Hosts>
<Host xsi:type="MailHost">
<DesktopFormFactor>
<!– Location of the Functions that UI-less buttons can trigger (ExecuteFunction Actions). –>
<FunctionFile resid="functionFile" />
<ExtensionPoint xsi:type="Events">
<Event Type="ItemSend" FunctionExecution="synchronous" FunctionName="onSendEvent" />
</ExtensionPoint>
…
</DesktopFormFactor>
</Host>
</Hosts>
…
</VersionOverrides>
</VersionOverrides>
[/code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The important thing to note here is that your FunctionFile function for OnSend will need to be defined OUTSIDE the scope of the Office.initialize() function. It needs to be GLOBAL. So you simply place it in the root of your functionFile.js. However, you still need to define an Office.initialize() function in your FunctionFile and in there you probably will want to grab a reference to the mailbox, mailbox.item and the user maybe and place them in a global var as well. This way you will have access to those things in the Event method to be able to make call like body.getAsync() or subject.getAsync(), etc.
/// <param name="event" type="object">ItemSend event is automatically passed by on send code to the function
// specified in the manifest.</param>
sendEvent = event;
// do your work here… since it will likely be ASYNC, you will want to
// set the event handler to a global. When you are done, you will issue
// > sendEvent.completed({ allowEvent: true }); // to send
// > sendEvent.completed({ allowEvent: false }); // to block
doSomething.Async(values, asyncComplete);
}
[/code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Then later in your code, you can either Cancel the send or allow the send event to occur, like this:
[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
function asyncCompelte() {
// procress the result
if(isOkToSend() == true) {
sendEvent.completed({ allowEvent: true }); // send it
} else {
sendEvent.completed({ allowEvent: false }); // block it
}
}
[/code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Once you have set the policy on the mailbox, modified your manifest and then added the event function to your FunctionFile, you are good to go. I will be working on a simple demo to place on GitHub in coming weeks and will post an update when complete.
However, effectively immediately, all the code pages I was using now display an error like this all over my site:
This is VERY frustrating I know.
I have decided I will be converting all my code blocks over to Gist. Unless anyone has a better idea for a WordPress site. P.S. I really do NOT want to use add-on’s either. Anyway, this may take me a while as I have a LOT of code content out there on Docs.com. So, please bear with me.
Also, please note, I (kind of) anticipated this. In all my code pages there is a link above every Docs.com embedding you can click to expand and to see the full code on my site:
Recently, I was working on an Outlook Visual Studio Tools for Office (VSTO) solution and encountered a problem with unexpected results with the Restrict() method. What I was doing was trying to locate all the appointments (meeting and recurrence) that occurred today. The Restrict command was returning items where the Start and End date properties that were from 2000, 2001, 2010, etc. I quickly deduced that it was also returning recurring items that were dated back to the first occurrence. Here is what my code looked like:
[code lang=”csharp” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
///
<summary>
/// EXTENSION METHOD
/// Get all the appointments between the given date times
/// </summary>
/// <param name="PobjItems"></param>
/// <param name="PobjStart"></param>
/// <param name="PobjEnd"></param>
/// <returns></returns>
public static List<Outlook.AppointmentItem> FindAppointments(this Outlook.Items PobjItems,
DateTime PobjStart,
DateTime PobjEnd)
{
try
{
List<Outlook.AppointmentItem> LobjReturn = new List<Outlook.AppointmentItem>();
string LstrCriteria = "[Start] >= \"" + PobjStart.ToString("g") + "\" AND [End] <= \"" + PobjEnd.ToString("g") + "\"";
// we want to find the oldest first. This way we do
// not get stuck on all day appointments that are conflicting
// with the current value.
PobjItems.Sort("[End]");
PobjItems.IncludeRecurrences = true;
PobjItems = PobjItems.Restrict(LstrCriteria);
object LobjItem = PobjItems.GetFirst();
// chcek the appointments or meetings against the
// the rules and set Skype status
Outlook.AppointmentItem LobjAppt = null;
do
{
if (LobjItem is Outlook.MeetingItem)
{
LobjAppt = ((Outlook.MeetingItem)LobjItem).GetAssociatedAppointment(false);
}
else
{
LobjAppt = (Outlook.AppointmentItem)LobjItem;
}
// as long as we are not null
if (LobjItem != null)
{
LobjReturn.Add(LobjAppt);
LobjItem = PobjItems.GetNext();
}
}
while (LobjItem != null);
// done
return LobjReturn;
}
catch (Exception PobjEx)
{
PobjEx.Log(false);
return null;
}
}
[/code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
What I created is an extension item off the Outlook.Items object that allows me to easily restrict them to appointments found between two given dates. This is actually a very useful extension method. If it worked the way I expected it to. What I found out is that in order to get recurring items that occur today which have todays date in the Start and End date properties, you MUST sort the results by the [Start] date. You cannot sort by the [End] date as I had done. Almost exactly the same, here is the fixed function:
[code lang=”csharp” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
///
<summary>
/// EXTENSION METHOD
/// Get all the appointments between the given date times
/// </summary>
/// <param name="PobjItems"></param>
/// <param name="PobjStart"></param>
/// <param name="PobjEnd"></param>
/// <returns></returns>
public static List<Outlook.AppointmentItem> FindAppointments(this Outlook.Items PobjItems,
DateTime PobjStart,
DateTime PobjEnd)
{
try
{
List<Outlook.AppointmentItem> LobjReturn = new List<Outlook.AppointmentItem>();
string LstrCriteria = "[Start] >= \"" + PobjStart.ToString("g") + "\" AND [End] <= \"" + PobjEnd.ToString("g") + "\"";
// FIX: we must use [Start] in the Sort to get the right recurrence
// for todays date…
PobjItems.Sort("[Start]");
PobjItems.IncludeRecurrences = true;
PobjItems = PobjItems.Restrict(LstrCriteria);
object LobjItem = PobjItems.GetFirst();
// chcek the appointments or meetings against the
// the rules and set Skype status
Outlook.AppointmentItem LobjAppt = null;
do
{
if (LobjItem is Outlook.MeetingItem)
{
LobjAppt = ((Outlook.MeetingItem)LobjItem).GetAssociatedAppointment(false);
}
else
{
LobjAppt = (Outlook.AppointmentItem)LobjItem;
}
// as long as we are not null
if (LobjItem != null)
{
LobjReturn.Add(LobjAppt);
LobjItem = PobjItems.GetNext();
}
}
while (LobjItem != null);
// done
return LobjReturn;
}
catch (Exception PobjEx)
{
PobjEx.Log(false);
return null;
}
}
[/code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Recently, I was asked what is needed in order to create the most basic add-in you can. The least amount of files, work and effort. Technically, you only need two files:
An HTML file with all your JavaScript inline and all the Office and JQuery libraries being pulled from a CDN.
An XML Manifest file that defines the add-in.
You publish the HTML file to a web server, get the address to where it is published, update the manifest with that information and then install the XML file as an add-in. The XML file will define the add-in name/description and points to the HTML file for the code. When you launch the add-in, the task pane will open and the HTML page will be loaded in it. From there your JavaScript will execute and your add-in will come alive. It is THAT easy. 2 files.
To demonstrate this, I created a simple Excel add-in. Here is the code for the bare minimum manifest XML:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Here is the HTML file that is quite basic, I am not even using the Microsoft Fabric for the controls. This is a simple HTML page, period:
[code lang=”xml” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<title>Excel Basic Add-In</title>
<!–using JQuery CDN so we do not need to include–>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" type="text/javascript"></script>
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>
<script type="text/javascript">
(function () {
"use strict";
// The initialize function must be run each time a new page is loaded.
Office.initialize = function (reason) {
$(document).ready(function () {
// Add a click event handler for the button.
$(‘#simple-button’).click(function () {
Office.context.document.getSelectedDataAsync(Office.CoercionType.Text,
function (result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
$("#banner-text").text(‘The selected text is: "’ + result.value + ‘"’);
$("#banner").show();
} else {
$("#banner-text").text(‘Error: ‘ + result.error.message);
$("#banner").show();
}
});
});
$("#banner-close").click(function () { $("#banner").hide(); });
$("#banner").hide();
});
}
})();
</script>
</head>
<body>
<div id="content-main">
<div class="padding">
<h1>Basic Addin</h1>
<div>Select a cell with text and then…</div>
<button id="simple-button">Click Here!</button></div>
</div>
<div id="banner" style="position:absolute;bottom: 0;">
<div id="banner-text"></div>
<button id="banner-close"> <i>X</i> </button></div>
</body>
</html>
[/code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
What this add-in does is very simple. It opens the task pane with a single button. Enter some text in a cell in Excel, select that cell, and then click the button. It gets the text and displays it in a popup at the bottom of the pane. I have published this add-in manifest here:
Open Excel Online. I first log into https://outlook.office365.com. Then I click on the menu button in the upper left and select Excel.
I select to create a “New blank workbook.”
On the Insert tab, I click Add-ins.
In the upper right, I select “Upload My Add-in”
I click Browse and in the Filename box, I put https://basicaddin.azurewebsites.net/basicaddin.xml and then click Open. This will download the add-in to the cache on your system and you will get a file name like “basicaddin[1].xml.”
Click Upload.
The add-in will load from my Azure site and you will see the button demonstrated above.
Now, to go ahead and answer a question I know are coming. How did I upload this Azure without Visual Studio publishing tools:
I went to my Azure Protal (portal.azure.com).
I clicked the +New button on the left side.
I selected Web + Mobile, then Web App
I filled in the required information: Name and resource group and clicked Create.
Then I used the steps above to load it into Excel Online.
This is fairly simple from a web developer perspective. Now, if you are not a web developer (but a traditional Office developer), you are probably still on the fence on how/why you would want to go through all of this. Again, the primary thing I am demonstrating here is how you can build an add-in with as few files as possible. But, I want to reiterate how these new add-ins are truly cross-platform. Follow these steps on your iPad and you will know what I mean. It is incredible and this work really is not that difficult to perform. It might just be a tad time consuming in the beginning as you are getting used to the new interfaces.
If you have questions, please feel free to reach out to me.
I am asked the following questions all the time by customers interested in making the jump to Office.js. Therefore, I am providing this FAQ as a primer which usually helps open the door to deeper discussions:
What is Office.js exactly?
Office.js is a new way to develop customizations (or add-ins) for Office that are cross-platform. It is developed in JavaScript, runs from a webserver and loads into a content pane or task pane in the document or application.
Cross-platform?
Yes. You can currently install or use an Office application (Word, Excel, PowerPoint and Outlook), on any of these platforms: Windows, Mac, iOS, Android (soon) or Web. There are some restrictions on what is supported and how much of the toolset is available per platform. See this article: Office Add-in host and platform availability
Is Visual Basic for Macros (VBA), Visual Studio Tools for Office (VSTO), COM Add-ins or Macros going away then?
No. Office.js is just another development platform for Office. There are no plans to deprecate any of the other programmability options for Office. Office.js was designed with the Cloud First, Mobile first vision at Microsoft and expands customizations across multiple platforms. It can be thought of as add-ins that you write once, run anywhere.
I have an existing COM add-in. Do I need convert it to Office.js?
No. Unless of course you want your add-in to work across multiple platforms. However, it is important to consider what your add-in is doing. The current application programming interfaces (APIs) for Office.js are more limited. While they are increasing in capability every day, they are still not on parity with COM, VBA or VSTO.
Can I convert my add-in in Visual Studio?
No. This is because the new Office.js developer platform is completely new. There are no conversion tools. If you want to make the move to Office.js, you will likely need to start from scratch. However, if you have properly separated your backend logic from application interfaces, you might be able to reuse some of your existing code as a Web Service.
How do determine if my existing COM add-in can be rewritten in Office.js?
This is harder to answer. The short answer is you try to create it in the new platform and see how much you can get to function. Longer answer, there are a couple of approaches:
You rewrite the entire thing in the new platform and where some things do not translate or work the same, you change the requirements to meet the new platform restrictions, such as changing the workflow, number of features or expected results.
You write as much as you can in the new platform and keep building on it as the platform continues to take shape. Whatever you cannot accomplish in Office.js you write as a VSTO add-in.
You provide a more limited Office.js version for the non-Windows based platforms you support but still use your COM or VSTO add-in on Windows.
How do I develop an Office.js add-in?
There are a number of editors you can use. These are web based so technically you can use any platform you want. I suggest using Visual Studio where you have templates the provide you with predefined templates for Word, Excel, PowerPoint and Outlook Web Add-ins. Also, the Publishing process is much simplified in my opinion with the Visual Studio tools.
How do Office Web Add-ins work?
Office Web Add-ins are web pages. You publish the web pages to a web server and the entire solution will load and run from there. As part of developing the add-in, you will create an XML file called a manifest. The Manifest in simplest terms will define the name of your add-in, a unique ID for your add-in, its version, a description, any ribbon commands and the URL to the website. In Office you can then add the manifest and this will register the add-in in your Office application and load the ribbon buttons defined. When the user clicks the ribbons the manifest will tell Office what URL to load and then the add-in will appear in a content pane or task pane.
What if I have more questions?
Please ask in the comments section and I will work to continue to expand this FAQ.
I have been working with Outlook Web Add-ins and Compose Message integration for Office Outlook Web Access for a while now. That is, Outlook add-ins that present themselves when the user is composing an email. This scenario is not seemingly as common. But one of my customers has been completely gung-ho about getting all of their COM add-ins converted to the new Office.js model. Some of their solutions will be able to make it there, and some will not. We are finding some roadblocks with mail compose apps.
One area that is currently failing is setting X-headers on an email item. I developed a JavaScript library to assist with this: easyEws. I blogged about it here. The one specific EWS SOAP call my customer wants to use from that library is this one:
updateEwsHeader: Updated the named x-header in the message.
This function works great in two conditions:
Outlook Web Access (or Office 365 Outlook Online), and
Outlook client – Online mode only
If you use Outlook client (Outlook 2016) in cached mode, this call will seem to work (because it does), but the x-headers will be missing when the user presses “Send.” The problem is that while the currently composed item is saved to the drafts folder and the custom x-headers are written to it via the EWS call (server side), the current Outlook Inspector/session is NOT updated. The cached Drafts folder may take (depending on network latency) anywhere from 1 to 3 minutes to be updated, but regardless the item in the current inspector session will NOT ever be updated. So when the user presses SEND, the item in the drafts folder will be overwritten with one that does not contain the custom x-headers and then sent.
There is no way around this limitation. And I have confirmed this with the Exchange and Office.js teams that this is a limitation of cached mode in Outlook. If you have a need for custom x-headers with an Outlook add-in your only option for now is to create (or keep your existing) COM/VSTO add-in.
If you are running into this limitation, please let me know by commenting below.
You know, sometimes the way you want to do things is not necessarily the right way. And even then the right way makes life so hard you want to cry. With Office.JS I recently blogged about “a way” you can test multiple sprints of your add-in in your development environment: Testing a Web Add-in with Multiple Manifest. In this article I explain a method of creating multiple manifests to point to each “version or sprint” of your add-in. Recently, I found that one of my customers threw a wrinkle into that idea. Not that creating multiple manifests was impossible, but they were needing two conflating factors:
Each sprint points to a different folder of HTML/JS.
And they needed to use query string parameters (like this post) to load one of maybe 20 different configuration settings.
With up to 20 configurations per sprint and lets say 4 sprints, you end up with 80 manifest files. So, this is untenable. You need more choices, then enter necessity…
So, lets say you need to test multiple configurations and multiple sprints. Here is how you will set this up:
You will publish each sprint of your Web Site Project into a different folder in IIS. Like this:
Different sprints in separate folders
Then, you will create a new HTML page. I called mine Launcher.html. This page will contain a submit button and two drop-down select controls:
One for sprints
One for configs
Next, you will modify your manifest. You can add a new button called “Sprint Testing” for example and have it launch: Launcher.html.
In the Launcher.JS, populate those dropdowns. You can populate these hard coded into the HTML, from your client side JS or call your service controller. In my example, I call my service controller.
When the user clicks the submit button after choosing the desired settings, you will build the URL to the proper sprint folder and then append the config values to the current query string (that last item is critical so that your sprint Compose/Read page will get the important Office related query string values passed to it). You will see I do this below by replacing my “launcher.html”, replacing it will the new path, then append the config setting:
Once done your code will perform a location.replace() on the url and you will have your proper sprint and configuration loaded. You will only have a single manifest file and you will be able to test/regress test as much as needed with different sprints and configurations.
Here is what the HTML page I created looks like when loaded:
Here is the HTML:
[code lang=”javascript” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
<!DOCTYPE html>
<html>
<head>
<title>Select Which Sprint to Launch</title>
<meta charset="utf-8" />
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>
<script src="Scripts/jquery-3.1.1.js" type="text/javascript"></script>
<script src="launcher.js" type="text/javascript"></script>
</head>
<body>
<h3>Please select the sprint you wish to launch:</h3>
<select id="selectSprintList">
</select>
<h3>Please select the configuration:</h3>
<select id="selectConfigList">
</select>
<button id="launchSprintButton">Submit</button>
</body>
</html>
[/code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
[code lang=”javascript” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
/// <reference path="Scripts/jquery-3.1.1.js" />
(function () {
‘use strict’;
Office.initialize = function (reason) {
$(document).ready(function (reason) {
/// Upon load connect to the server and request
/// the sprints so that we can fill the select
/// field on the form.
makeAjaxCall("GetSprints", null, function (response) {
/** @type {String} */
var data = response.Message.toString();
/** @type {String[]} */
var results = data.split(",");
$.each(results, function (index, value) {
$("#selectSprintList").append("
<option>" + value + "</option>");
})
}, function (error) {
$(document).append("
" + error + "
");
});
makeAjaxCall("GetConfigs", null, function (response) {
/** @type {String} */
var data = response.Message.toString();
/** @type {String[]} */
var results = data.split(",");
$.each(results, function (index, value) {
$("#selectConfigList").append("
<option>" + value + "</option>");
})
});
$("#launchSprintButton").click(function (event) {
/// The user clicked the submit button. Build the URL
/// from the selection in the select control and
/// change the location
var sprint = $("#selectSprintList").val();
var config = $("#selectConfigList").val();
var url = "";
if (sprint != null && sprint != "" &&
config != null && sprint != "") {
// replace the launcher page with the proper sprint folder location,
// but be sure to keep all the query string values – to pass on
// to our OfficeJS page. However, add our one config setting to the end
url = window.location.href.replace("launcher.html", sprint + "/ComposeMessage.html");
url += "&Config=" + config;
// replace the url
location.replace(url);
}
});
});
}
})();
// Helper function to call the web service controller
makeAjaxCall = function (command, params, callback, error) {
var dataToPassToService = {
Command: command,
Params: params
};
$.ajax({
url: ‘api/Default’,
type: ‘POST’,
data: JSON.stringify(dataToPassToService),
contentType: ‘application/json;charset=utf-8’,
headers: { ‘Access-Control-Allow-Origin’: ‘*’ },
crossDomain: true
}).done(function (data) {
callback(data);
}).fail(function (status) {
error(status.statusText);
})
};
[/code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
And here is the code for the controller, now, I cheated and just returned a string value for each call, but this at least help you get the idea of how to build your own list for sprints and configs:
[code lang=”javascript” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace OutlookLauncherDemoWeb.Controllers
{
public class DefaultController : ApiController
{
///
<summary>
/// Service Request – incoming
/// </summary>
public class WebServiceRequest
{
public string Command { get; set; }
public string[] Params { get; set; }
}
///
<summary>
/// Service Response – outgoing
/// </summary>
public class WebServiceResponse
{
public string Status { get; set; }
public string Message { get; set; }
}
[HttpPost()]
public WebServiceResponse Values(WebServiceRequest request)
{
WebServiceResponse response = null;
switch (request.Command)
{
case "GetSprints":
return getSprints();
case "GetConfigs":
return getConfigs();
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
The idea here is that this add-in is a baseline for yours. You can build these components into a debug build of your add-in and it will call into each separate folder/sprint where you have posted previous versions.
There is a common problem I see when automating Excel from an external .NET application:
You will launch Excel, open a workbook, perform work, and then try to close Excel and find that it remains in memory. You might even have code in your solution to try to re-use existing instance of Excel and find that it uses one of these “zombie” instances that do not have any of the add-ins loaded… and maybe an add-in your code relies on.
The issue is that Excel is not able to close because the primary thread of your application is holding on to a COM reference. Even adding Marshal.ComReleaseObject() and GC.Collect() like this probably will not improve the situation either:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
From my experience, and from what I have gathered is that until the thread is stopped, the COM reference will be locked. Not sure where the issue lies (EXCEL, COM, or .NET), but I have found a workaround to this. What you have to do is terminate your thread. But because you do not want to terminate your application, you need to create a NEW thread. So the following pattern is what I have found works:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters