Iframes unleashed in OutSystems

António Carvalho
ITNEXT
Published in
6 min readApr 19, 2024

--

The only way to have real decoupling between different teams or to implement “some kind” of micro-frontends architecture in OutSystems… is to use the good old iframe! Here are some useful tips I learned along the way.

Yeah… in the age of JavaScript frameworks and the all mighty ODC we still need to resort to iframes for complete separation of deployment pipelines when using OutSystems. 🥹

Why use iframes?

On a typical OutSystems factory, when you deploy an Application between environments all its hard dependencies must be deployed as well. And if those dependencies are consumed by other Applications, they must be deployed as well. 😒

This raises a very problematic issue: when something goes, all the hard connected dependencies must go as well! And it requires a lot of planning to sync the work of multiple teams working on different Applications.

Unlike other technologies, we cannot have multiple versions of the same package on the same environment. In ODC a Library can have multiple versions, but we are still far way from having ODC as a stable solution.

Using iframes, we can incorporate an application inside another application: and they are completely independent. They can even be served from different domains.

Another possible usage of iframes is when we have a Traditional Web Application and we want to migrate into Reactive Web App or ODC.
We can gradually develop certain parts of our application in RWA and replace them inside a legacy application… piece by piece, step by step.

These possibilities sound great, but there are several caveats… 💪

Possible issues

Lets list some challenges I faced when using iframes:

  1. Security
    … and everything relating CORS.
  2. Communication
    … between frames and data transport.
  3. Responsive behavior
    … or at least how OutSystems handles responsive behavior.
  4. Browser and iframe resizing
    … because content changes and browsers move a lot.

1. Security and CORS

When working with iframes we must consider CORS implementation.

Cross-origin resource sharing
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Cross-origin resource sharing is a mechanism that allows a web page to access restricted resources from a server on a domain different than the domain that served the web page. A web page may freely embed cross-origin images, stylesheets, scripts, iframes, and videos.

These restrictions make sense and are put in place as a security measure.

Nevertheless, with the proper configuration on the platform settings we can do pretty much everything.

We can configure to only incorporate iframes coming from the same domain or a specific third-party domain.

The OutSystems Factory Admins should be able to access those settings and allow/prevent whatever is needed. Here is a starting point: https://success.outsystems.com/documentation/11/security/apply_content_security_policy/

2. Communication

There are several ways to share data between different frames:

. localStorage

https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
If both frames are from the same origin, they share the localStorage items.

This way, we can keep some state tokens ready to be read and written by all frames. We should not store sensitive information this way, because localStorage is easily accessible and manipulated from the browser DevTools. 🥸

We can even listen to when the storage changes with window.addEventListener("storage”, … and react to any modification that happens on the localStorage entry.

. postMessage

https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
If we need to communicate between frames from different hosts (meaning cross-origin), we can use the window.postMessage()method.

Take a look at the following code:

// Emitter
// Assuming 'otherWindow' is a reference to another iframe's window object
const myMessage = 'Hello from the other side';
otherWindow.postMessage(myMessage, targetOrigin);
// Receiver
window.addEventListener('message', function(event) {
if (event.origin !== "http://expected-origin.com") {
console.log("Origin not trusted");
return;
}
console.log('Received message:', event.data);
// Process the event.data here
});

There are multiple security concerns. That’s a fact.
But they are also easily mitigated.

Check the link above to read all security related suggestions and you are ready to go. 🫡

The most important one… Always provide a specific targetOrigin. 🤝

.customEvent

When all the frames are from the same origin, the easiest way is to dispatch and listen to customEvents. They can be fully customized and transport any kind of data.

A custom event can be attached to the global window or to a specific DOM element.

For example, on the main screen holding the iframe, we can attach a listener on the .layout(all the screens have a layout, right?)

// add an appropriate event listener
const listener = document.querySelector(".layout");
listener.addEventListener("OPEN_SESAME", (evt) => {
console.log(evt.detail));
}

And on any given child iframe, if you have something like:

const listener = window.top.document.querySelector(".layout");
listener.dispatchEvent(new CustomEvent("OPEN_SESAME", {
detail: {
name: "John",
food: "potatoes"
},
}););

Notice the window.top? This way we can access the top level window and target it, when dispatching the event with any data.

Of course we can also access a child iframe and target its DOM elements dispatching custom events in the opposite direction:

let iframe = document.getElementById('myIframe');

// the second is for legacy browsers
let iframeDocument = iframe.contentDocument || iframe.contentWindow.document;

console.log(iframeDocument.body.innerHTML);

Again, this only works if both documents are from the same origin!

3. Responsive behavior

The OutSystems responsive implementation is very straightforward.
It adds the following CSS classes: desktop, tablet, phoneinto the document body. And the technique to evaluate which CSS class to apply is the screen width.

Below 768px it adds the .phoneCSS class.
Below 1024px it adds the .tabletCSS class.
Otherwise it adds the .desktopCSS class.

The OutSystemsUI patterns behave and are styled differently depending on which CSS class is present on the document body element.

The thing is, when using iframes the width of the iframe element may induce the screen to consider itself a tablet or a phone.

Although the top level window may have a width of 1600px, if the iframe is 500px wide, the screen inside of it will be considered a phone, and all the patterns will behave as such.

To avoid this, there is a client action SetDeviceBreakpoints from the OutSystemsUI application that allows us to set different values for the evaluation, when the screen resizes:

If you set 100px, everything above that will be considered desktop!

This way, all the screens will behave as desktop, no matter the iframe width.

Another approach I use, is to completely remove these CSS classes, because they are not really needed:

document.body.classList.remove("phone", "tablet", "desktop");

Mixing both techniques, the OSUI client action and some custom JavaScript, we are able to completely remove these unnecessary CSS classes from our iframes.

4. Browser and iframe resizing

I believe this topic is where the Frontend Engineering craftsmanship becomes more relevant.

One of the problems when working with iframes is their size and scrolling behavior. I have worked on projects where several parts of the screen are inside iframes and the end-user has no idea of it. 😉

Screens filled with Menus, Datepickers, Dropdowns, Accordions and whatnot… and they all work together, the user resizes the browser and everything adjusts. And the iframes can even be served from different domains!

With a mix of CSS and JavaScript we can achieve so much.

Lets check this code:

const iframe = document.getElementById("#myIframe");
console.log(iframe.getBoundingClientRect());

This will return a DOMRect object, such as:

DOMRect {
bottom: 1294,
height: 1175,
left: 996,
right: 1703,
top: 118,
width: 706,
x: 996,
y: 118,
}

Now imagine you append those values as CSS variables:

const iframe = document.getElementById("#myIframe");
const rect = iframe.getBoundingClientRect();
iframe.style.setProperty("--iframe-top", rect.top + "px");

Will result in:

<iframe id="myIframe" style="--iframe-top: 118px" ...>

Now… if we have this in our CSS:


#myIframe {
height: calc(100vh - var(--iframe-top));
}

Et voilà! 😍

See what we have done here?

No matter how the content changes or the window resizes, the iframe height will be adjusted in real time to reach the bottom of the screen, by the CSS. Even if the content above it changes its layout.

Of course, this evaluation of the DOMRect should be done on every window resize event.

Conclusion

In large factories we want real separation between teams and deployment cycles. But sometimes we also want these applications to work together on the same screen and interact.

In OutSystems, using iframes is still the best (and only!) way to do it.
That’s a sad reality. 🥹

If you find yourself in a big project with the need to use iframes, I hope this article may help you navigate through the obstacles and facilitate the Frontend Engineering implementations.

What are your thoughts on this?

If you enjoyed this article, press the Clap button 👏

Check out the other articles and reach me in the comments!
Visit my Youtube Channel

--

--

Writer for

From the 90's websites, moving through the golden age of Flash and the birth of JavaScript frameworks, I now joined the low-code ecosystem with OutSystems.