Hello Marketplace Developer community! Welcome back for the second instalment of A Day in The Life of a Marketplace Partner!
I have to shamefully admit that there have been a staggering 311 days between posts in this series, perhaps disqualifying it for being a series at all. (un)fortunately I had to fill most of those days with actually being a Marketplace Partner instead of writing about it.
I hope I will be able to spent more time on these in-depth analyses of the intricacies involved in app building in the future. One of the reasons I’m somewhat optimistic about being able to turn this into a more regular thing is that I’ve been spending the first days of the new year completely rewriting our DC polyfill for Atlassian Connect.
Why built a Atlassian Connect polyfill, you ask? As we try to optimise code re-use between Cloud & DC, our goal is to minimise platform specific front-end code. One of the ways we achieve this is by heavily using Inversion of Control / Dependency Injection and React Context. And that helps us a lot, but it does not solve the issue of the dependency on AP.
In order to also use AP within the context of the DC platform, we need to polyfill the AP object. Not only that, we are also running our DC code in an iframe. This allows us to isolate our code, preventing UI blocking and global variable sharing / dependency hell. But it also means that we need to polyfill the Simple-XDM “host” part of Connect.
Instead of using Simple XDM, we’ve decided to create our own implementation, because with a lot of things Atlassian, Simple XDM, even though it is called Simple, is anything but simple. We tried porting it, but given that this was made for a different use case (it is strongly tied to the concept of creating an extension framework) we went for an alternative solution.
The benefit of doing so also means that we have been able to increase our understanding of the Atlassian Connect framework, as we had to reverse engineer exactly how everything works in order to be able to implement it. As everyone who has ever worked with anything Atlassian knows, the documentation only tells you 20% of the story.
I really expect that this journey will prove to be a treasure trove for more posts about working with Atlassian Connect, but for now, let’s start with the first one!
The hidden gem that is
There comes a time in every app developers life that they have to open a dialog. If you do this within the context of the Atlassian products, you might consider using Modal Dialog only to quickly find out that this will open the dialog within the boundaries of the iframe. The blanket will not fill the entire page, only your small subsection. It will look horrible.
So you turn to AP.dialog, which solves this issue. In return, you will get a whole new level of headaches to deal with.
AP.dialog.create creates a dialog within the context of the host product, and it will load the contents of the dialog in yet another iframe. In order to communicate between your dialog iframe and your non-dialog iframes you have some convenience methods to your disposal.
Let start with the two most obvious ones that you will need:
Every dialog that is opened, will at some point also be closed. And if you do close it, you might want to let your non-dialog calling iframe know. You may even want to pass it some data. Also, if you have a dialog with chrome (header & footer) you will probably also have buttons. You can also listen to button click events using
AP.events.on() from the non-dialog iframe.
Based on the documentation, these two methods might seem pretty straightforward: you open a dialog, you close it and you handle the close event.
But if you’re dealing with implementing a polyfill, you want to learn about all the nitty gritty details. And that is when it get’s interesting.
Dialogs are disconnected
You might think that the dialog is inherently connected to the iframe in which
AP.dialog.create() was invoked. They are not. Simply put, a dialog is just an AUI Dialog2 with some additional css class (
ap-aui-dialog2). They are appended to the host product
<body /> tag.
The result is that
AP.dialog.close() has some interesting behaviour:
When invoked from a non-dialog iframe
If you invoke the
AP.dialog.close() command from a non-dialog iframe, the host Simple XDM implementation will simply remove the last
section.ap-aui-dialog2 node from
<body />. The dialog that was opened last will be closed first.
I MEAN THIS LITERALLY
It will remove the last node, regardless of who created that node. That means that calling
AP.dialog.close() from App 1 can, and will close a dialog opened by App 2. This method is app agnostic.
It does not care about hierarchy either: if you use
AP.dialog.create in the non-dialog iframe to create dialog A, then create dialog B from dialog A and create dialog C from a non-dialog iframe the last created dialog (dialog C) will be closed as this is the last item in de node list.
When invoked from a dialog iframe
In this case,
AP.dialog.close() will also be a simple DOM manipulation, but instead of only closing the dialog related to the dialog iframe in which the method is invoked, it will actually look up the
section.ap-aui-dialog2 related to the iframe and remove that node AND all next sibling
section.ap-aui-dialog2 nodes it will find on body.
Consider creating dialog A, dialog B, dialog C and dialog D (in that order) and then call
AP.dialog.close() from the iframe of dialog B, it will close B, C and D, as C and D are next sibling nodes of dialog B.
This is also app agnostic. If dialog B and D were created for App 1, and dialog C for App 2, it will still close all these dialogs.
To trigger or not to trigger, that is the question
AP.dialog.close() behaves somewhat erratically it becomes even more important that you receive the events associated with the closing of the dialog, right?
Well yes, but unfortunately, that is not the case. For instance, the
dialog.close event is not fired when
AP.dialog.close() is triggered from a non-dialog iframe, even if that non-dialog iframe is from another app. That means that App 2 can close a dialog created for App 1 and App 1 will never be notified it got closed
dialog.close event does get fired properly when
AP.dialog.close() is invoked in the dialog iframe, unless there are nested dialogs. If you open dialog A, B and C, and you invoke
AP.dialog.close() on dialog B, both B and C will be closed but you will only receive a single
dialog.close event (for dialog B). Again, even if dialog C is associated with App 2.
Let me start by reiterating that the reason we found out how
AP.dialog.close() behaves is because we are re-implementing it for DC and that 99.9% of app developers will never have an issue with it. You will probably not see a lot of apps with multiple nested dialogs, or have nested dialogs opened from different apps at the same time.
It is also highly unusual to not close the last opened dialog as you do not have UI access to that dialog if there is another dialog opened. The implementation of
AP.dialog.close() by Atlassian basically follows the happy path of how you should work with dialogs.
But nonetheless I believe it is good to know how these things work, to remove a bit of the “magic box” feeling of working with apps. That is also why I highly recommend everyone to look at the Simple-XDM and the Atlassian Connect implementation. Special shoutout to the Atlassian Connect team for making the source of these libraries available
Oh, final disclaimer: please make sure not to use this information for malicious purposes it would be a shame if someone would create an app that would close every dialog opened, just because they can