OfficeJS.dialogs Updated (v1.0.5)

I have been working hard on my OfficeJS.Dialogs library and just published version 1.0.5. You can get it from NPM and GitHub. See my previous post for more information on how to do this.

I have added a few new features:

  • A simple Alert.Show() method that displays a simple OK box. For those times you want to just simply pop up a quick notification to the user.
  • A Progress.Show() that displays a progress bar. This allows for you to show the progress bar and then issue Progress.Update() to move the progress bar along. When you are done you call Progress.Complete().
  • A Wait.Show() dialog that will show an indeterminate spinner. This form will remain up until you issue a Wait.CloseDialog().
  • New UpdateMessage() and Update() methods were added to the MessageBox. This was done to allow you to quickly ask a lot of questions of the user in one instance of the dialog, without giving the user back to the application for a second while the new dialog is rendered. UpdateMessage() will just update the message but keep all the buttons the same, but you will specify a new callback. Update() will allow you to fundamentally change all the settings the MessageBox (buttons, icon, caption, text and all), plus a new callback function.
  • Behind the scenes I made some improvements/bug fixes:
    • If you try to show two dialogs too quickly, nothing will happen. So I added a half-second delay between dialog displays to make sure you never get an overlap.
    • You will get an error message in your callback if more than one dialog is attempted to be opened at once.
    • “Window Messaging” has been setup with Progress and MessageBox to allow the parent and the dialog to pass messages back and forth. It involves using setTimeout().

For those interested in the last item, here is what that look like:

        /**
        * Handles messages coming from the parent
        */
        function startMessageHandler() {
            setTimeout(function() {
                var message = localStorage.getItem("dialogMessage");
                localStorage.setItem("dialogMessage", ""); // clear the message
                if(message !== undefined && message !== null && message != "")
                {
                    var msg = JSON.parse(message);
                    if(msg.message == "update") {
                        // update the form
                        updateForm(msg.settings);
                    } else if(msg.message == "close") {
                        // do nothing special here
                        return; // stops the message pump
                    } else if(msg.message == "progress") {
                        if(msg.settings.Number > 100) return;
                        $("#bar").prop("value",msg.settings.Number);
                    }
                }
                startMessageHandler(); // call again
            }, 0);
        }

Here is an example of how to use the Progress dialog. The method signature is like this:

Progress.Show( [message], [start], [max], [completeCallback], [cancelCallback] )

  • The message is the message the user see’s when the dialog is opened.
  • The start is the number you want the progress bar to start at. Usually this should just be zero (0).
  • The max is the number you want the Progress bar to end at. Usually this should be 100. But it can be any number you want. If you have 5 steps to perform in the background while this dialog is up, you can set this to 5.
  • The completeCallback is your callback function to be called when your code calls the Progress.Compelte().
  • The cancelCallback is what gets called when the user presses the Cancel button on the form.

By itself, this will do nothing. You will have to call the Progress.Update() command in order to move the progress bar, or update the message to the user. Here is the method signature for the Update method:

Progress.Update( [amount], [message] )

  • The amount is how much you want the progress bar to move. If you do not specify an amount, an amount of 1 is assumed.
  • The message is a new message to provide the progress bar. If you want to update the message and do not want to increment the progress bar, specify an amount of zero (0).

Once you are all done with the Progress dialog, you issue a Progress.Complete() call. There are no parameters to it. Once called, your completeCallback in the Progress.Show() call will then be executed.

Here is an example:

// reset first to make sure we get a fresh object
Progress.Reset();
// display a progress bar form and set it from 0 to 100
Progress.Show("Please wait while this happens...", 0, 100, function() {
    // once we are done - when your code
    // calls Progress.Complete()
    Alert.Show("All done folks!");
  }, function() {
    // this is only going to be called if the user cancels
    Alert.Show("The user cancelled");
});
doProgress();

function doProgress() {
  // increment by one, the result that comes back is
  // two pieces of information: Cancelled and Value
  var result = Progress.Update(1);
  // if we are not cancelled and the value is not 100%
  // we will keep going, but in your code you will
  // likely just be incrementing and making sure
  // at each stage that the user has not cancelled
  if(!result.Cancelled && result.Value <= 100) {     setTimeout(function() {       // this is only for our example to       // cause the progress bar to move       doProgress();     },100);   } else if(result.Value >= 100) {
    Progress.Compelte(); // done
  }
};

That example also uses the new Alert dialog. This one is very simple:

Alert.Show ( [message] )

This next example uses the new Wait dialog, which is much simpler to implement. Here is the method signature:

Wait.Show( [message], [showCancel], [cancelCallback] )

  • The message is the message you want to show the user. If you specify null, it will appear as simply “Please wait…”
  • The showCancel flag if set will allow the user to see a Cancel button.
  • The cancelCallback function is only valid if the showCancel option is true. When the user presses cancel, this function gets called.

When you are ready to close the Wait dialog, you issues a Wait.CloseDialog(). Here is an example:

  Wait.Show(null, true, function() {
    Alert.Show("The user cancelled.");
  });
  setTimeout(function(){
    Wait.CloseDialog();
    Alert.Show("Done!");
  }, 15000);

If you have some suggestions for some things you would like to see added to this library, please add a comment below or reach out to me on LinkedIn or Twitter. Some ideas I will be working on:

  • Allow you to call another dialog type without having the close the dialog.
  • A selection dialog, where you have a dropdown list of a listbox where you wan select (or multi-select) items.
  • An option to resize forms.
  • An option to use the message handler in your own custom form – minimal code

Dialogs in OfficeJS

I have been working on a number of projects for my customers and recently, dialogs have taken front and center stage. The Office.context.ui Dialogs are powerful, albeit a tad confusing and the documentation suffers from a few easily missed points.  Here is the documentation:

But in this post, I hope to explain everything I have learned. To start off with here is the code to issue the dialog:

  Office.context.ui.displayDialogAsync('https://localhost:3000/function-file/dialog.html',
      { height: 20, width: 30, displayInIframe: true },
      function (asyncResult) {
          dialog = asyncResult.value;
          // callbacks from the parent
          dialog.addEventHandler(Office.EventType.DialogEventReceived, processMessage);
          dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage);
      });

What this dialog does is opens as a modal form in a frame over the application (in Office Web Apps). It looks like this:

screen.PNG

As you can see the dialog is modal. But what is really important are the two event handlers you need to register to be able to get back to your code:

          dialog.addEventHandler(Office.EventType.DialogEventReceived, processMessage);
          dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage);
The first one is an event receiver and really this is the event handler for errors, such as being unable to open the dialog or, most importantly, the user closed the dialog by clicking the (X) in the upper right of the dialog. There are a series errors you can catch, but specifically, the dialog cancel is this:
12006 The dialog box was closed, usually because the user chooses the X button.
The second one is a handler for messages coming from the dialog. These messages can be anything, but is usually a string or a JSON string. You can send a message from the dialog like this:
Office.context.ui.messageParent('{message}');
When the dialog issues a message using the code above, the function defined in the event handler is called. For example, if the user clicks an OK button or Submit button, you can pass the stringified values from the form back to the callback function. From there the dialog actually remains open until the caller issues a close, like this:
    // close the dialog
    dialog.close();
In the example above where I make the displayDialogAsync call, you will see I defined the SAME callback function for both dialog events. I did this because the results can be parsed by the same function. Here is what my function look like:
function processMessage(arg) {
    // close the dialog
    dialog.close();
    // procress the result
    if(arg.error == 12006) {
      // user clicked the (X) on the dialog
      sendEvent.completed({ allowEvent: false });
    } else {
      if(arg.message=="Yes") {
        // user clicked yes
        sendEvent.completed({ allowEvent: true });
      } else {
        // user clicked no
        sendEvent.completed({ allowEvent: false });
      }
    }
}
My previous blog post references all the code for the dialog:

Yo Office

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. wlEmoticon-hotsmile.png
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:
  1. Change directory (using cd) to the folder where you want the project to live.
  2. Type “yo office” and then answer the questions.
  3. 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:

yo.PNG

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. wlEmoticon-surprisedsmile.png 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…
vscode.PNG
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.. wlEmoticon-disappointedsmile.png
  • 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. wlEmoticon-hotsmile.png 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:

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.

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

Simple Web Service Controller for Office Add-in

I have found Web Service Controllers in Office Add-ins to be quite useful. There are a number of reasons you might want to keep functions of your add-in on a server, including obfuscation, complex calculation, data intense, service mashups, and much more. However, every time you want to use your Web Ser4vice from the JavaScript interface, there is a lot of code associated with the AJAX call you have to make. This begs for simplicity and that is what I have done. In my App.js project, I added a simple makeAjaxCall function that takes the command you want to invoke, the parameters you want to pass it (as an array) a callback when the call is complete and a callback if there was an error. Here is the core code for the makeAjaxCall() method:

// Helper function to call the web service controller
app.makeAjaxCall = function (command, params, callback, error) {
	var dataToPassToService = {
		Command: command,
		Params: params
	};
	$.ajax({
		url: '../../api/WebService',
		type: 'POST',
		data: JSON.stringify(dataToPassToService),
		contentType: 'application/json;charset=utf-8'
	}).done(function (data) {
		callback(data);
	}).fail(function (status) {
		error(status);
	})
};

Here is a sample of my web service controller:

/// <summary>
/// CORE SERVICE FUNCTION
/// This function will take a web request which will contain the command the caller wants
/// to initate and then the paramaters needed for that call. We enter a SELECT statement
/// below to determine the command given and we then call the proper helper function
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost()]
public WebServiceResponse WebService(WebServiceRequest request)
{
	WebServiceResponse response = null;
	switch(request.Command)
	{
		case "DoFunctionA":
			return functionA(request.Params);
		case "DoFunctionB":
			return functionB(request.Params);
		case "DoFunctionC":
			return functionC(request.Params);
		case "DoFunctionD":
			return functionD(request.Params);
	}

	response = new WebServiceResponse();
	response.Message = "Unknown command";
	return response;
}

And here is how you call it:

app.makeAjaxCall("DoFunctionA", ["Value1", "Value2"], function (data) {
var result = $.parseJSON(data.Message);
// do something with the retuirned result here...
});

For more information on how to create a Web Service controller, there is a great blog post from a colleague of mine, Michael Zlatkovsky on how to do this:

Create a web service for an app for Office using the ASP.NET Web API

[UPDATE] In which Application is my Add-in running?

UPDATE: The code outlined in this post will no longer work with Office Online. See this post for more information: http://stackoverflow.com/a/32851938/678505. Specifically, this block:

Update Dec 5, 2016: We will soon be releasing an API to detect the platform info (partially in response to the fact that the _host_info URL paramater, which folks had unofficially relied on, needed to be recently removed for Office Online). We also have a temporary workaround in anticipation of the forthcoming official API. See “In Excel Online, OfficeJS API is not passing the host_Info_ parameter anymore to Excel Add-In” for information on the API and workaround.

Original post:

In case you missed the news, there was an update to the Office API’s. Version 1.2 was announced at //build and released 2 weeks ago. This API enhancement gets us closer to parity with the native object models used in VBA and VSTO. One interesting aspect of the new Office add-ins (formally known as Apps for Office) is how you can use the same codebase across all the applications. However, as the API becomes richer they are also becoming more product specific. Meaning, there is now the Excel.run() and Word.run() commands (PowerPoint is a little lagging in this area, but it is supposedly on the horizon).  These allow you to do Excel Workbook (or Word document) context specific commands. And there is a lot of other goodies (like promises). But, I digress… wlEmoticon-hotsmile.png

I was working on a proof of concept for my customer when I found that I needed to use the same codebase for a lot of the same work, but in some cases I needed to do something specific in Word and in another, something specific in Excel. I came up with the following function that I placed in my App.js file:

var current;
app.hostTypes = { Word :"Word", PowerPoint :"PowerPoint", Excel :"Excel"};
app.getHost = function () {
	if (current == null) {
		if (Office.context.requirements.isSetSupported('WordApi')) {
			current = app.hostTypes.Word;
		} else if (Office.context.requirements.isSetSupported('ExcelApi')) {
			current = app.hostTypes.Excel;
		} else {
			var host = $.urlParam("_host_Info");
			if (host.toLowerCase().indexOf("word",0) &gt;= 0) {
				current = app.hostTypes.Word;
			} else if (host.toLowerCase().indexOf("excel",0) &gt;= 0) {
				current = app.hostTypes.Excel;
			} else {
				current = app.hostTypes.PowerPoint;
			}
		}
		return current;
	}
	else {
		return current;
	}
};

To use this you would do this for Word:

            /// WORD
            if (app.getHost() == app.hostTypes.Word) {
                // Word specific code where
            }

Or, this for Excel:

            /// EXCEL
            if (app.getHost() == app.hostTypes.Excel) {
                // Excel specific code here
            }