OfficeJS: Second Dialog Does not Display

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. wlEmoticon-disappointedsmile.png

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]


var i = 0;
function displayDialog() {
var url = "https://localhost:3000/test.html&quot;;
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();
}
});
}
});
}

view raw

dialogIssue.js

hosted with ❤ by GitHub

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]


/**
* 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);
}

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&quot;;
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]


var i = 0;
function displayDialog() {
var url = "https://localhost:3000/test.html&quot;;
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();
}
});
});
}
});
}

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. wlEmoticon-hotsmile.png

3 thoughts on “OfficeJS: Second Dialog Does not Display”

Leave a Reply