I have recently updated the OfficeJS.dialogs to version 1.0.8. I have also updated the Node Package Manager version as well. You can get it by running the following command in the VSCode Terminal Window:
npm install officejs.dialogs
With this latest version comes a few bug fixes, some cleanup of the code, cleaner JSDoc documentation and a PrintPreview dialog. The PrintPreview started out as a proof of concept but it actually works for me – in the browser anyway. I am not sure how well it will work in any of the full clients as I have not tested it there yet. If anyone has a chance to test it, please let me know if you encounter any issues.
Here is a sample of the PrintPreview dialog:
Here is some code to implement it:
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
This has been slow coming, but I have just released v1.0.7 of easyEws. As per usual, it is on NPM and on GitHub. You can get the latest with the command:
npm update easyews
Or, you can install it into your solution with the command:
npm install easyews
In this version the JSDOC information is now full incorporated so you get great inline help with each command. I enhanced the library so that it works better with inline help and so I changed how the class is setup and exposed. I have updated the README.md on GitHub, but I still am working on examples and such, that will be done later. And I added new functionality, a moveItem command which can be used to move any item from one folder to another. This was a added due to feedback I received on LinkedIn.
Please feel free to reach out to me with any questions or suggestions.
If you have been developing OfficeJS add-ins for Outlook Online (or Exchange 2016 Outlook Web Access) you might not know that you now have the ability to create OnSend add-ins. That is add-ins that can trap the send of an email, test for conditions (such as getBodyAsync() or getSubjectAsync() to look for content) and if they fail, prevent the message from being sent. I recently blogged about it here:
First thing to note about these add-ins is that they cannot be created for or uploaded into the store. The second thing to note is that they MUST be configured first in order to run. The third thing to note is that if you are building an on-prem Exchange based solution, you have to have Exchange 2016 CU6 installed and a bit more. The full details of how to configure the environments (either Office Online or On-prem) are detailed here:
I have been spending a lot of time in the Officeui.dialog lately. One of my customers has been too and it has been an adventure working out exactly the best way to get messages displayed while running code in the background asynchronously.
I am not sure if this problem is limited to the online version of Outlook, but this is where I have been seeing the problem (and where I have spent virtually all of my time). If my code tried to open two dialogs (using Office.context.ui.displayDialogAsync()) one right after the other, the second dialog would not ever be displayed. If I waited a period and then tried again, it would. But we don’t want that. We want boom-boom, dialogs. When I looked at the console, I would see an error like the following:
Uncaught TypeError: Cannot read property ‘addEventHandler’ of undefined
Or, if I read the error object from the displayDialogAsync() function/asyncResult, I would see this:
{name: “Display Dialog Error”, message: “The operation failed because this add-in already has an active dialog.“, code: 12007}
Here is an example of some code that will reproduce the issue:
[code lang=”javascript” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
var i = 0;
function displayDialog() {
var url = "https://localhost:3000/test.html";
Office.context.ui.displayDialogAsync(url,{height:20, width:30, displayInIframe:true},
function (asyncResult) {
var dialog = asyncResult.value; // get the dialog
var error = asyncResult.error;
if(dialog == undefined && error.code > 0) {
// log the error
console.log(error.message);
} else {
// attache the events
dialog.addEventHandler(Office.EventType.DialogEventReceived, function (arg) {
// close this dialog, open the next
dialog.close();
i++;
if(i<4) {
displayDialog();
}
});
dialog.addEventHandler(Office.EventType.DialogMessageReceived, function (arg) {
// close this dialog, open the next
dialog.close();
i++;
if(i<4) {
displayDialog();
}
});
}
});
}
[/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
Notice I had used dialog.close(), but it did not work as designed. What I believe is happening is that the previous dialog is still in memory and has not been cleaned up. What needs to likely happen is a closeAsync().
In order to resolve this, I created the following function: dialogCloseAsync(). This works by issuing the close() and then attempting to add an event handler to the dialog in ansyc (setTimeout) loop. When it errors, we trap the error and issue the async callback. It is a bit ugly as we are trapping an error to get around the problem, but this was the only way I could find a way around the problem. Here is what the function looks like:
[code lang=”javascript” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
/**
* Closes the currently open dialog asynchronously.
* This has an ugly workaround which is to try to set a new
* event handler on the dialog until it fails. When it failed
* we know the original dialog object was destroyed and we
* can then proceed. The issue we are working around is that
* if you call two dialogs back to back, the second one will
* likely not open at all.
* @param {Office.context.ui.dialog} dialog The dialog to be closed
* @param {function()} asyncResult The callback when close is complete
*/
function dialogCloseAsync(dialog, asyncResult){
// issue the close
dialog.close();
// and then try to add a handler
// when that fails it is closed
setTimeout(function() {
try{
dialog.addEventHandler(Office.EventType.DialogMessageReceived, function() {});
dialogCloseAsync(dialog, asyncResult);
} catch(e) {
asyncResult(); // done – closed
}
}, 0);
}
[/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
I had been encountering this issue with different systems when developing the OfficeJS.dialogs library and had tried to set a timeout before I showed each dialog. That worked on some systems, but on others the timeout needed to be longer. So, setting a default timeout did not work. Using this in the original sample, provided above, the code would look like this:
[code lang=”javascript” collapse=”true” title=”click to expand if the github.com embedding below is not visible.”]
var i = 0;
function displayDialog() {
var url = "https://localhost:3000/test.html";
Office.context.ui.displayDialogAsync(url,{height:20, width:30, displayInIframe:true},
function (asyncResult) {
var dialog = asyncResult.value; // get the dialog
var error = asyncResult.error;
if(dialog == undefined && error.code > 0) {
// log the error
console.log(error.message);
} else {
// attache the events
dialog.addEventHandler(Office.EventType.DialogEventReceived, function (arg) {
// close this dialog, open the next
dialogCloseAsync(dialog, function() {
i++;
if(i<4) {
displayDialog();
}
});
});
dialog.addEventHandler(Office.EventType.DialogMessageReceived, function (arg) {
// close this dialog, open the next
dialogCloseAsync(dialog, function() {
i++;
if(i<4) {
displayDialog();
}
});
});
}
});
}
[/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
As I found this workaround, I have updated OfficeJS.dialogs to use dialogCloseAsync(). Now, the MessageBox, Wait and Form objects will use closeDialogAsync() commands to replace the original closeDialog() commands I provided previously. I will be blogging about the updates to v1.0.6, shortly.
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:
[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
/**
* 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);
}
[/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 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:
[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
// 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
}
};
[/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 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:
[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
Wait.Show(null, true, function() {
Alert.Show("The user cancelled.");
});
setTimeout(function(){
Wait.CloseDialog();
Alert.Show("Done!");
}, 15000);
[/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
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
Several customers have asked me if OfficeJS has something similar to a Visual Basic or C# MessageBox.Show() function. The answer is no. And for a long time there was not even a dialog option. With the latest releases of the OfficeJS libraries comes a new dialog feature. Yet to get a standard MessageBox, you will still need to create it from scratch. Or, at least until this blog post you did. I have created a helper library the consists of two files:
dialogs.js
dialogs.html
To reference this library you can do any of the following:
NOTE: This assumes your page is in the root of your project. The key point is that it is added to your node_modules when you use NPM and this is how you will reference it.
Once referenced you can then call a MessageBox like this:
[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
MessageBox.Reset();
MessageBox.Show("This is a test with a lot of text that simply has not point but to show you what " +
"happens when you place a lot of text in a MessageBox for any point other than to " +
"place a whole lot of text in the MessageBox.",
"This is the Caption",
MessageBoxButtons.AbortRetryCancel,
MessageBoxIcons.Stop,
true, "Check this if you want me to stop nagging you.",
function(buttonPressed, checked) {
console.log("The button pressed was: " + buttonPressed);
console.log("The checkbox was checked? " + checked);
});
[/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 embedding below is not visible.”]
InputBox.Reset();
InputBox.Show("What value do you want to enter?",
"InputBox caption",
"Default value", function(result) {
var msg = "";
if(result.length == 0) {
msg = "The user pressed cancel.";
} else {
msg = "The user entered: " + result;
}
console.log(msg);
});
[/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
You can show a custom form of your own design like this:
[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
Form.Reset();
Form.Show("/test.html",10,20,false,function(result){
var yourJSON = JSON.parse(result).Result;
// if you placed false in the 4th param you must
// handle the close of the form
Form.DialogClose();
});
[/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
Here is an example of what the above code looks like:
It is important to note that like everything else in the OfficeJS world this is an async dialog. This also means it is non-blocking. This means any code you do not have in your callback method will continue to run. And if you are wanting to display multiple message boxes at the same time – you cannot. The last one you try to display wins, the others will be gone. Most everything in this dialog is just like you will remember from the good ol’ Visual Basic/C# MessageBox and InputBox. Even the constants for MessageBoxButtons and MessageBoxIcons are the same. But, I added a little flare and it probably helps with the best practices in OfficeJS to not nag the user with dialogs, and that is the ability to add a check box to the form so you can ask the user if they do not want to be bothered by your code anymore.
For the MessageBox, the withcheckbox and checkboxtext are there to give you that ability. Additionally, you see the callback method (asyncResult) that will return once the use dismissed the dialog. It will return with two pieces of information:
The button the user clicked in string format. So “Yes” or “Cancel” will be what you see here.
A Boolean representing whether the check box was checked or not.
For the InputBox, the callback method (asyncResult), will return one piece of information. If will return the text the user entered, or it will return nothing (an empty string), if the user pressed cancel.
The Form method will return a JSON object:
Error: { }, // Error object
Result: { }, // JSON from form
Cancelled: false // boolean if formed cancelled with X
The Result object will be the JSON from your own form. In your code you will need to call back to the parent like this:
[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
Office.initialize = function(reason) {
$(document).ready(function () {
$("#okButton").click(function() {
// notify the parent
Office.context.ui.messageParent("My custom message.");
});
});
};
[/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
You will also see in the examples above, I call .Reset() before I issue a new dialog request. This is because the objects are global and this is a way to be certain to clean up anything in memory that might be associated with a previous dialog. In my testing, I never really had problems like this, but I added it as an extra precaution.
Also, note, I have only tested this in Outlook OWA, I have not had a chance to test it in Excel, Word, PowerPoint or even in the full Outlook client. So, if you encounter issues in those other clients, please let me know.
Finally, I want to call out the OfficeJS Helpers. This library provides a lot of help with authorization dialogs, but also has a simple method for displaying messages using OfficeHelpers.ui.notify(). You can install it into your project using NPM:
As I delve ever deeper into the world of Node, I have found the ability to install packages with NPM quite handy. I do this from VS Code using the Terminal window. I just type:
npm install <packagename>
As I have been developing different packages for my customers, I have found the need to install code that I have been reusing over and over again. Most importantly, easyEws. So, I created a npmjs account (davecra) and I published easyEws. But what is even better and what I was after, is I can now install the latest version of easyEws by going into the Terminal window in VS Code and typing this:
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:
[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
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);
});
[/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 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:
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:
[code lang=”javascript”]
// close the dialog
dialog.close();
[/code]
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:
[code lang=”javascript” collapse=”true” title=”click to expand if the embedding below is not visible.”]
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 });
}
}
}
[/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 sample add-in does is pops up a dialog box anytime you press Send on an email with a question: “Are you sure?” If you click Yes, it sends, if you click “no” is blocks the send. Here is what that dialog looks like:
If you have followed the steps from my Yo Office post, you will be able to open the project in VS Code, and from the terminal, launch it with:
npm start
Next, you will need to follow the steps to enable the OnSend policy in your account and then you will need to install/sideload the manifest. From there you will not see any appearance of the add-in at all, until you press the Send button.
NOTE: At the point I first tested it on June, 13th, 2017, there is a KNOWN ISSUE with the certificates created fro a “yo office” build. If you have not resolved the certificates issue you will be able to start the project in Node.js, but the add-in will fail to run. You need to perform the steps outlined here to correct it:
I have been working on improving my various Outlook proof-of-concepts and nearly every one of them require using EWS to access data about items in Exchange, which are not exposed through the object model. As such I have been constantly improving easyEWS.js. I have just posted version 1.0.6 and also have provided a published CDN which you can now use to always get the latest version of the library. Here is the CDN:
Typed every var using JSDoc. See my post on that here. This should help with debugging any issues you have. And if you have issues with my library, please contact me.
Updated the Error handling callbacks and the Debugging callback to include more information. Every function can be called with this pattern, and you will get more information now if something does wrong. Here is the essential pattern every function uses:
[code lang=”javascript” collapse=”false”]
easyEws.fn([x, [y],] function(z) {
// this is the success
}, function(error) {
// this is the failure – error
}, function(debug) {
// this is the debug information which contains:
// the soap message sent to the server
// the soap response from the server
// the server status message
// the server error message if any
}