OfficeDev / office-js

A repo and NPM package for Office.js, corresponding to a copy of what gets published to the official "evergreen" Office.js CDN, at https://appsforoffice.microsoft.com/lib/1/hosted/office.js.
https://learn.microsoft.com/javascript/api/overview
Other
670 stars 96 forks source link

Outlook classic: Office.onReady gets not invoked in pinned tabs when switching folders #4806

Open Flyingdot opened 3 weeks ago

Flyingdot commented 3 weeks ago

In classic Outlook on Windows, when the user switches folders, a pinned Add-In does not invoke Office.onReady (or Office.initialize). On all other platforms, everything works as expected.

Your Environment

Expected behavior

Office.onReady gets invoked the same way I would start the Add-In (or refresh it).

Current behavior

Office.onReady gets not invoked.

Steps to reproduce

  1. Pin the Add-In in Outlook
  2. Switch to a different folder

Link to live example(s)

The behavior is reproducible with a basic hello-world example:

<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Minimal Sample</title>

        <script
            type="text/javascript"
            src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js"
        ></script>

        <script defer>
            Office.onReady(function () {
                console.log('ready')
                console.log(Office.context.diagnostics.version)
            })
        </script>
    </head>

    <body></body>
</html>

Context

We are bootstrapping our application (Vue) when Office.onReady gets invoked, so this behavior breaks our entire Add-In in the pinned scenario.

Adrian-MSFT commented 2 weeks ago

Hi @Flyingdot, can you give a little more info about your scenario? Would using the Office.EventType.ItemChanged event work in your case? https://learn.microsoft.com/en-us/javascript/api/office/office.eventtype?view=common-js-preview#fields

Flyingdot commented 2 weeks ago

As I wrote, we are bootstrapping our Vue application based on Office.onReady. Because this is not called, the application is not started either. So, using the ItemChanged event is not an option here because the (pinned) AddIn is reloaded when the folder in Outlook is changed and after this we don't have an application instance.

Here is our bootstrapping logic (with a history fix to avoid problems with the Vue router; see #2808 and others).

function boot(): Promise<void> {
    const replaceState = window.history.replaceState
    const pushState = window.history.pushState

    return new Promise((resolve, reject) => {
        const script = document.createElement('script')
        script.src = 'https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js'

        script.onload = () => {
            Office.onReady(({ host }) => {
                // this does not happen, so our application is not started
                window.history.replaceState = replaceState
                window.history.pushState = pushState

                if (host) {
                    resolve()
                } else {
                    reject(new Error('The application has to run from within Office'))
                }
            })
        }

        document.body.appendChild(script)
    })
}

// bootstrap application when Office is ready
boot().then(() => const app = createApp(App))

I tried a few more things, and I noticed that if I delay the loading of the page by at least 500ms, it works because office.OnReady is then called.

Adrian-MSFT commented 1 week ago

@Flyingdot In the win32 platform, not re-running Office.onReady or Office.Initialize is intended behavior - Correct me if I'm wrong, but at a high level, it sounds like you need to run the code within the Office.onReady function whenever the selection is changed, which is what the ItemChanged event is designed to do.

Can you provide more info on why the itemChanged event does not work for your case? You mentioned that you don't have your application instance. Can you run your application initialization code on the itemChanged event callback function?

Flyingdot commented 5 days ago

I see now that my current behavior is not what you describe. In my case, the Add-In gets reloaded on each folder switch - but it does not when switching the selected E-Mail, which is not the case "normally." This was misleading to me. I'm digging into it to find out what is triggering this behavior.

We are already using the ItemChanged-Event.

Flyingdot commented 5 days ago

Okay, I was able to track down the confusing behaviour. The issue happened because we reloaded the Add-In immediately inside the ItemChanged-Handler.

Here is a sample for easy reproduction of the behavior:

<!doctype html>
<html lang="en">
    <head>
        <script src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js"></script>
    </head>
    <body class="bg-slate-100">
        <div id="app"></div>

        <script>
            console.log('Add-In loaded')

            Office.onReady(({ host }) => {
                console.log('Office.onReady', host)

                Office.context.mailbox.addHandlerAsync(Office.EventType.ItemChanged, () => {
                    console.log('ItemChanged Handler')
                    window.location.reload()
                })
            })
        </script>
    </body>
</html>

When switching folders, this code results in the following log outputs everywhere except Classic Outlook on Windows:

ItemChanged Handler
[Page Load]
Add-In loaded
Office.onReady - Outlook

In Classic Outlook on Windows, however, switching folders results in this:

ItemChanged Handler
[Page Load]
Add-In loaded

So Office.onReady is not invoked, which is wrong, in my opinion, because we are doing a full refresh of the page here. It works as expected on all other platforms.

There is a workaround for this. It works if we delay the refresh of the page like this:

Office.context.mailbox.addHandlerAsync(Office.EventType.ItemChanged, () => {
  console.log('ItemChanged Handler')

// delay the reload, then it works as expected
  setTimeout(() => {
    window.location.reload()
  }, 500)
})

This workaround leads to the same log outputs as on the other platforms:

ItemChanged Handler
[Page Load]
Add-In loaded
Office.onReady - Outlook
timwan10 commented 4 days ago

I think there may still be some timing issues here. But one thing that is different about the clients, is that Win32 Classic will "unload" an item on the ItemChanged Event. When it does this, it sends a "null"/"undefined" item when the previous item unloads from the reading pane. Though sometimes if it already has the new item, it will skip this and just send you the new item. Doing the folder switch is likely causing enough delay that you will get the null item events, and this could be causing the issue when you do a window.location.reload when you have an undefined item.

Can you adjust your code to see if this fixes the issue?

                Office.context.mailbox.addHandlerAsync(Office.EventType.ItemChanged, () => {
                    if (Office.context.mailbox.item != undefined)
                    window.location.reload()
                })

Why do you need to do a window.location.reload()? This somewhat takes the point out of the itemChanged handler. The point of that event is to prevent the need to refresh the page, and just update the item object with the latest update so that the entire webpage doesn't have to be loaded again?