Jun 25, 2020

Seamlessly Integrating Micro Apps with iFrame

A recent client wanted to upgrade a small portion of their legacy application with a more modern UI and extra functionality, like fuzzy text search. There are a few approaches to incremental upgrades of legacy applications. Some are non-starters, such as wrapping the application in a single project that matches the new technology you plan to use, e.g., Webpack, VueCLI or CreateReactApp.

What other options do we have? Remember iFrames? That thing we were all supposed to forget about and never use again? Our case is the perfect use case. Dropping in additional functionality into legacy applications. The problem that still needs to be solved is, how can these applications communicate with each other?

The browser API has a window method called postMessage, which allows applications to create message events that can be caught by any child of the window environment. We can use this to pass JSON objects between the main window and the iFrame.

The example I will show here, is one that dynamically creates an iFramed app, uses postMessage to provide context data, waits for a postMessage event after some work is performed, then destroys the iFrame.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<html>

<head>
  <style>
  #popup {
    position: fixed;
    top: 50px%;
    left: 15%;
    min-width: 1024px;
    min-height: 768px;
    width: 70%;
    height: 70%;
    background-color: white;
    z-index: 10;
  }
  .show {
    display: block;
    -webkit-animation: fade-in .3s ease-out;
    -moz-animation: fade-in .3s ease-out;
  }
  .hide {
    display: none;
  }
  #popup iframe {
    width: 100%;
    height: 100%;
    border: 0;
  }
  #popupdarkbg {
    position: fixed;
    z-index: 5;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-color:
    rgba(0,0,0,.75);
  }
  @-webkit-keyframes fade-in {
    0% { opacity: 0; }
    100% { opacity: 1; }
  }
  @-moz-keyframes fade-in {
    0% { opacity: 0; }
    100% { opacity: 1; }
  }
  </style>
  <script type="text/javascript">
  function sendiFrameData() {
    try {
      let message = {
        type: 'mainAppMessage',
        payload: {}
      };
      let frame = document.getElementById('popupiframe');
      frame.contentWindow.postMessage(message, '*');
    } catch (e) {
      console.log(e) // eslint-disable-line no-console
    }
  }
  function openiFrame() {
    console.log('---Opening IFrame---') // eslint-disable-line no-console
    document.getElementById("popup").classList.remove("hide");
    document.getElementById("popupdarkbg").classList.remove("hide");
    document.getElementById("popup").classList.add("show");
    document.getElementById("popupdarkbg").classList.add("show");
    document.getElementById('popupiframe').src = "http://iframeAppURL:8080";
  }
  function closeiFrame() {
    console.log('---Closing IFrame---') // eslint-disable-line no-console
    document.getElementById("popup").classList.remove("show");
    document.getElementById("popupdarkbg").classList.remove("show");
    document.getElementById("popup").classList.add("hide");
    document.getElementById("popupdarkbg").classList.add("hide");
  }

  window.onload = function() {
    document.getElementById("link").onclick = function(e) {
      e.preventDefault();
      openiFrame();
      document.getElementById('popupdarkbg').onclick = function() {
        closeiFrame();
      };

      window.addEventListener('message', function(event) {
        if (event.data.type === 'iframeAppMessage') {
          console.log('<<< Got a Message from iFrame <<<') // eslint-disable-line no-console
          console.log(event.data.payload)
          closeiFrame();
        }
      });

      return false;
    }
  };
  </script>
</head>

<body>

  <div id="main">
    <h1>Hi I am Main App</h1>
    <button id="link">Click me to open iframe app</button><br>
  </div>

  <div id="popup" class="hide">
    <iframe id="popupiframe" onload="sendiFrameData()"></iframe>
  </div>
  <div id="popupdarkbg" class="hide"></div>

</body>

</html>

Below is code in the iFrame, written in VueJS, showing how to catch the message from the main app, and communicate back with postMessage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<script>
export default {
  name: 'Home',
  data() {
    return {
      crmContext: null
    }
  },
  created() {
    window.addEventListener('message', (event) => {
      const { data } = event
      const { type, payload } = data
      if (type === 'mainAppMessage') {
        console.log('>>> Got a Message from Main App >>>', {data}) // eslint-disable-line no-console
        this.crmContext = event
      }
    })
  },
  methods: {
    done() {
      const message = {
        type: 'iframeAppMessage',
        payload: {}
      }
      if (this.crmContext && this.crmContext.source) {
        this.crmContext.source.postMessage(message, '*')
      } else {
        console.log('Could not post message', {message}) // eslint-disable-line no-console
      }
    }
  }
}
</script>

Using this these methods, the integration and communication is a seamless user experience, and allows developers to incrementally add drop-in functionality without overhauling the legacy app.

About the Author

Corey Webster profile.

Corey Webster

Sr. Consultant
Leave a Reply

Your email address will not be published.

Related Blog Posts
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]
Building Better Data Visualization Experiences: Part 1 of 2
Through direct experience with data scientists, business analysts, lab technicians, as well as other UX professionals, I have found that we need a better understanding of the people who will be using our data visualization products in order to build them. Creating a product utilizing data with the goal of providing insight is fundamentally different from a typical user-centric web experience, although traditional UX process methods can help.
Kafka Schema Evolution With Java Spring Boot and Protobuf
In this blog I will be demonstrating Kafka schema evolution with Java, Spring Boot and Protobuf.  This app is for tutorial purposes, so there will be instances where a refactor could happen. I tried to […]
Redis Bitmaps: Storing state in small places
Redis is a popular open source in-memory data store that supports all kinds of abstract data structures. In this post and in an accompanying example Java project, I am going to explore two great use […]