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
657 stars 93 forks source link

How to detect active Extension Point? #2482

Closed jfoclpf closed 2 years ago

jfoclpf commented 2 years ago

In Outlook Addin, for Windows and Outlook 365, imagine you have an ItemSend event handler combined with other Extension Points in the same manifest, like Appointment Compose or Appointment Read.

How do you distinguish in JS code which is the active Extension Point being used, with a single Addin?

I am struggling on this for 2 full days in a row and I am posting this here because nothing appears in Google nor in your documentation.

The Event Handler is triggering functions which were not supposed to, because they're inside Office.onReady

exextoc commented 2 years ago

Every ExtensionPoint can be pointed at it's own unique URL, so you can point them to different files and detect it that way.

I'm guessing you want to use the same file for all extension points. So the common solution to that is to add a query parameter to the end of your urls in your manifest file.

i.e.

https://contso.com/mypage.php?extensionPoint=button1 https://contso.com/mypage.php?extensionPoint=button2

Then you can get the Query Parameter in your JS code.

If you only care about the differences between Appointment Compose / Read, then you can get the ItemType from office.context.mailbox.item

jfoclpf commented 2 years ago

But please, please, please, how such manifest would be? It's not yet clear for me how to combine different FunctionFiles with different ExtensionPoints?

<Hosts>
  <Host xsi:type="MailHost">
    <DesktopFormFactor>
      <FunctionFile resid="Taskpane.Url"/>
      <ExtensionPoint xsi:type="AppointmentOrganizerCommandSurface">
      <!--some stuff-->
      </ExtensionPoint>
      <ExtensionPoint xsi:type="AppointmentAttendeeCommandSurface">
      <!--some stuff-->
      </ExtensionPoint>
      <ExtensionPoint xsi:type="Events">
        <Event Type="ItemSend" FunctionExecution="synchronous" FunctionName="validateBody" />
      </ExtensionPoint>
    </DesktopFormFactor>
  </Host>
</Hosts>

I mean, do I duplicate the number of DesktopFormFactor or Host, since FunctionFile must be inside DesktopFormFactor?

jfoclpf commented 2 years ago

If you only care about the differences between Appointment Compose / Read, then you can get the ItemType from office.context.mailbox.item

Are you sure? The last time I tried it was just distinguishing between appointment and message

https://docs.microsoft.com/en-us/javascript/api/outlook/office.mailboxenums.itemtype?view=outlook-js-1.5&preserve-view=true

exextoc commented 2 years ago

Sorry I apologize, I thought you were trying to distinguish between separate Taskpane URLs. For Taskpanes the solution DOES work since each Taskpane has a separate URL:

                     <Action xsi:type="ShowTaskpane">
                        <SourceLocation resid="composeTaskPaneUrlOutlookJs" />
                      </Action>
...
        <bt:Url id="composeTaskPaneUrlOutlookJs" DefaultValue="https://contoso.com/page.html?queryparam=blah"/>

You would just have a separate Resid/URL for each one.

For ExecuteFunction, each ExecuteFunction could/should have it's own function that is called then you know what extension point it is coming from.

            <ExtensionPoint xsi:type="MessageComposeCommandSurface">
...
                        <Action xsi:type="ExecuteFunction">
                          <FunctionName>displayDialogMessageCompose</FunctionName>
                        </Action>

And in another ExtensionPoint:
            <ExtensionPoint xsi:type="MessageReadCommandSurface">
...
                        <Action xsi:type="ExecuteFunction">
                          <FunctionName>displayDialogMessageRead</FunctionName>
                        </Action>

So you should just know if displayDialogMessageCompose is called you are coming from the MessageComposeCommandSurface Extension Point.

exextoc commented 2 years ago

For distinguishing between AppointmentCompose and AppointmentRead, you were right. The ItemType is not enough (it only separates from Message / Appointment).

You can use the same strategy (by having separate functions or query parameters for taskpane to distinguish between them)

There is also a set of functions under office.cast.item i.e.: Office.cast.item.toMessageCompose(Office.context.mailbox.item)

the list of functions is:

toItemRead
toItemCompose
toMessageRead
toMessageCompose
toMeetingRequest
toAppointmentRead
toAppointmentCompose

These functions will throw an exception, if they are called on Office.context.mailbox.item and the item does NOT match the type they are checking.

They will return the item if they DO match the type.

I cannot seem to find the official documentation for these, so I will have to look at getting that fixed.

jfoclpf commented 2 years ago

Thank you so much, I am struggling with this for 2 days :)

And how do I detect the ItemSend event?

<ExtensionPoint xsi:type="Events">

Can I have Execute function

<Action xsi:type="ExecuteFunction">

inside this ExtensionPoint?

jfoclpf commented 2 years ago

Furthermore, how can I execute Event function validateBody

      <ExtensionPoint xsi:type="Events">
        <Event Type="ItemSend" FunctionExecution="synchronous" FunctionName="validateBody" />
      </ExtensionPoint>

only when taskpane is open/visible?

Basically in my Taskpane JS code I'm trying to emulate a tipical JS event handler, something like having an onItemSend like other events in Office.EventType.

But since they load in different instances, how can I know in the ItemSend instance that the Taskpane instance is open?

jfoclpf commented 2 years ago

I think I get it how to solve it @exextoc :)

<Hosts>
  <Host xsi:type="MailHost">
    <DesktopFormFactor>
      <FunctionFile resid="FunctionFile.Url"/>
      <ExtensionPoint xsi:type="AppointmentOrganizerCommandSurface">
        <Action xsi:type="ShowTaskpane">
          <SourceLocation resid="composeTaskPaneUrlOutlookJs1" />
        </Action>
        <!--some stuff-->
      </ExtensionPoint>
      <ExtensionPoint xsi:type="AppointmentAttendeeCommandSurface">
        <Action xsi:type="ShowTaskpane">
          <SourceLocation resid="composeTaskPaneUrlOutlookJs2" />
        </Action>
        <!--some stuff-->
      </ExtensionPoint>
      <ExtensionPoint xsi:type="Events">
        <Event Type="ItemSend" FunctionExecution="synchronous" FunctionName="validateBody" />
      </ExtensionPoint>
    </DesktopFormFactor>
  </Host>
</Hosts>

And then FunctionFile, composeTaskPaneUrlOutlookJs2, and composeTaskPaneUrlOutlookJs2 will point to the same URL with different HTTP GET parameters

Just remains a last question: how can I detect in the ItemSend event instance that the Taskpane is opened?

exextoc commented 2 years ago

There is currently no way to know if the Taskpane is currently open or not. This would be a new feature request that you can make at [UPDATE: Fixed link] https://aka.ms/M365dev-suggestions

It is possible to do the following:

jfoclpf commented 2 years ago

There is currently no way to know if the Taskpane is currently open or not. This would be a new feature request that you can make at https://officespdev.uservoice.com/forums/224641-general/category/131778-outlook-add-ins

Thank you, but that link does not work

  • Detect if the taskpane was EVER opened, by having your taskpane set a custom property that the ItemSend instance can check. However, because there is no way to remove the custom property when the Taskpane is closed by the user, there is no way to remove it when it is closed.

This would be great if the taskpane had a onClose event handler

But much simpler and practical would be to have the ItemSend as a common Office.EventType and then simply using addHandlerAsync

exextoc commented 2 years ago

Sorry updated the correct link here: https://aka.ms/M365dev-suggestions

What's the scenario you are trying to accomplish with needing to know if the Taskpane is open during ItemSend?

jfoclpf commented 2 years ago

Sorry updated the correct link here: https://aka.ms/M365dev-suggestions

What's the scenario you are trying to accomplish with needing to know if the Taskpane is open during ItemSend?

Done it

https://techcommunity.microsoft.com/t5/microsoft-365-developer-platform/outlook-itemsend-event-as-a-standard-office-eventtype-usable-in/idi-p/3260807

jfoclpf commented 2 years ago

What's the scenario you are trying to accomplish with needing to know if the Taskpane is open during ItemSend?

We're using the addin to send Outlook Appointments' data to an internal server for internal data sharing and statistics. The addin fetches appointment data from Outlook, the user further provides some further data (internal classification for example) in a form available in the Taskpane, and then they click a button and the Outlook Appointment data plus data from the Taskpane's form is sent to an internal server.

In the Taskpane we have a HTML button to send that data when the button is clicked. But we wanted to send that data also when the user clicked Send button in Outllook to have in the server the latest updated Appointment/Form data.

But we want to give the user control when the data is sent, and thus we want to be clear with the user that data is sent only when they click the Addin Button on the ribbon and the Taskpane is visible. Furthermore we need to fetch also data from the Taskpane form, before sending it to the server.

As I proposed in your portal, the best and simplest solution would be to have ItemSend event as a common Office.EventType handled in the Taskpane JS code.

exextoc commented 2 years ago

In this scenario it would probably work if the Taskpane saved the form's contents to Custom Properties, when the user has finished editing it. (perhaps you can use Javascript handlers to detect when the form has changed). As well as remove those custom properties if the user sends them manually.

Then in the ItemSend, transmit those properties if they are there. Or if you need to let the user have more control, let the ItemSend add-in give the user the option to transmit those properties if they are there. (pop up a dialog via DisplayDialogAsync) or display a notification message to open the taskpane.

jfoclpf commented 2 years ago

I already do that, every time the user edits any form entry, I have handlers to store that data into custom properties automatically.

Yes, a nice idea would be to save a custom property every time the user sends the data to the server manually by clicking the button, and then reading that wasButtonClicked custom property in the Item Send event function.

Thanks for the tips