Press "Enter" to skip to content

Send data between SalesForce and embed front-end JavaScript (Vue, React, Angular) app + development environment based on mock up data

I have described in this article HERE how to embed your own custom front-end app inside SalesForce page with Aura Component. I mean by “Front-end” app, the application written in VueJS, ReactJS, Angular or in pure JavaScript. I will continue here article mentioned earlier.

Embedding you custom front-end gives really huge possibilities of extending your SalesForce instance. You can build custom application in any JavaScript framework, and next, integrate it with SalesForce. For sure here you will need to exchange some data between SalesForce and custom front-end app.

Scroll this page to the final solution – skip introduction and local environment creation

Normal scenario would be to take some data from SalesForce, display in custom front-end (vue, react, angular) app, modify, and send back to SalesForce result data:

Data exchange between SalesForce and custom external app
Data exchange between SalesForce and custom external app

To make it happen, we must install in our front-end project the lightning-container npm package. In my case, I will extend here the my “External Front-end app for salesforce” external-frontend-app-for-salesforce VueJS application.

So what does the “Lightning container” npm package? Very good documentation of that you can find on SalesForce developer blog HERE, but I will describe it here in a few words.

Let’s say that this is a messaging broker between your custom JavaScritpt app and the hosting Aura Component (lightning container). It’s very simple, you can send message with data from your custom JavaScript app to SalesForce Aura component (lightning container) and, in opposite direction, you can receive the data message from SalesForce Aura component. So both instances, SalesForce and you custom embed app, can talk to each other. With LCC you can use 4 methods, but as I’m not a SalesForce develper, I’m interested only in first 3 methods:

  1. LCC.sendMessage(message) by this method we can send data to hosting Aura (lightning) component.
  2. LCC.addMessageHandler(handler) by this method we can receive data from hosting Aura (lightning) component.
  3. LCC.removeMessageHandler(handler) this method removes existing listener.
  4. LCC.callApex(method, params, callback, config) I will be honest – I’m front-end, not SalesForce, developer, I do not know how to use it 😉

This is a very similar communication interface like native JavaScript window.postMessage() and window.addEventListener("message", handler ) methods. You can find it’s very good description here on MDN. And next, before creating a connection to SalesForce, we will use native JavaScript to mock up the development SalesForce parent instance.

I have created example how to use window messages to send data between iframe and parent window by JavaScript – CHECK IT BELOW. You can download example HERE or check it on GITLAB. Check it below:

Check its code on screen shot below:

parent - childe javascript messages
parent – childe javascript messages

Example description

Our example will be very easy. SalesForce (and the same mock up local environment) will send JSON data with employees, next custom front-end JavaScript app will display it in table where employee’s salary will be editable. Edited employees data on button click will be send back to SalesForce Aura (lightning) component.

Data exchange between SalesForce and custom external app
Data exchange between SalesForce and custom external app

Local environment – mock up

As I mentioned earlier, LCC communication interface looks similar like native JavaScript window.postMessage() and window.addEventListener("message", handler ) methods. So with that we can build the development environment which would simulate for us the target connection between SalesForce and our VueJS app. You can find our VueJS application HERE on GITLAB

  1. We are sending “messages” – but what is a message? This should be not only the data, but headers + data. Headers are information which allows to identify the type of sent data, like endpoint in normal REST API. In my example I will use the following syntax of message, where instead of headers I have simply “type”:
    var message = {
      type: "emplyees-list",
      data: '{"example": "Some JSON DATA"}'
    };

    So message is in our case the JavaScript object, where with “type” property we can identify later what kind of message we have received in parent or child instance.

  2. We will reuse here example for iframe child and parent window data exchange, check it HERE or download HERE or check it on our GITLAB.
  3. But in above example we have as child only static iframe window, but we need to have there our VueJS external-frontend-app-for-salesforce app.
  4. So first let’s go into our VueJS external-frontend-app-for-salesforce app and let’s add here code for listening for messages from parent window (now in pure JavaScript, because we want to have similar local development environement, like we will have with SalesForce with LCC library.
  5. Before next points, where I explain in details how it works with code presentation, check this solution HERE on GITLAB (not master branch):
    sales-force-mockup folder for the SalesForce mock-up static window implementation
    src/App.vue file with VueJS component as child implementation
  6. As I code it in VueJS, I add in main component mounted hook and inside it I create event listener for receiving messages. Further I add a table to list employees, inputs to change its salaries, and at the end, I add submit button to sent modified employees list to parent (hosting) window.
    Final solution looks like below:
    App.vue file

    <template>
      <div id="app">
        <img alt="Vue logo" src="./assets/logo.png" class="logo">
        <h1>VueJS project for SalesForce</h1>
        
        <div v-if="employeesData.length">
          <h2>Employees list</h2>
          <button @click="sendData">Send data back</button>
          <table>
            <thead>
              <tr>
                <th>index</th>
                <th>age</th>
                <th>eyeColor</th>
                <th>name</th>
                <th>gender</th>
                <th>company</th>
                <th>email</th>
                <th>phone</th>
                <th>salary</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="row in employeesData" :key="row.index">
                <td>{{row.index}}</td>
                <td>{{row.age}}</td>
                <td>{{row.eyeColor}}</td>
                <td>{{row.name}}</td>
                <td>{{row.gender}}</td>
                <td>{{row.company}}</td>
                <td>{{row.email}}</td>
                <td>{{row.phone}}</td>
                <td><input type="number" v-model="row.salary"></td>
              </tr>
            </tbody>
          </table>
        </div>
    
      </div>
    </template>
    
    <script>
    
    export default {
      name: 'App',
      data: function () {
        return {
          employeesData: []
        }
      },
      computed: {
        employeesDataHeaders () {
          const firstRow = this.employeesData.length ? this.employeesData[0] : {};
          return Object.keys(firstRow);
        }
      },
      methods: {
        sendData () {
          const parentWindow = window.parent;
                    
          const message = {
            type: "employees-list",
            data: this.employeesData
          };
          
          parentWindow.postMessage(message, "*");
        }
      },
      mounted () {
        window.addEventListener("message", (e) => {
    
          let data = e.data ? e.data : {};
          let type = data.type;
          var content = data.data;
          
          if(type === "employees-list") {
            this.employeesData = JSON.parse(content);
          }
    
        });
      }
    }
    </script>
    
    <style>
    body {
      background: #fff;
    }
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 10px;
    }
    
    table {
      width: 100%;
      max-width: 1200px;
      margin: 20px auto;
    }
    
    button {
        padding: 10px;
        background: #07ab85;
        color: #fff;
        font-weight: bold;
        border: none;
        text-transform: uppercase;
        border-radius: 5px;
    }
    
    .logo {
      max-width: 100px;
    }
    </style>
    
  7. Last step is to change the parent static window html page into a SalesForce instance mock-up in sales-force-mockup folder – index.html / main.js / main.css files, its code is as following:sales-force-mockup/index.html
    REMEMBER: in iframe tag in src attr you must put URL to your running application. In my case it was: http://localhost:8080

    <!DOCTYPE html>
    <html>
        <head>
            <title>SalesForce mock-up</title>
            <meta http-equiv="expires" content="0">
            <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
            <script type="text/javascript" src="main.js" defer></script>
        <link type="text/css" rel="stylesheet" href="main.css">
        </head>
    
        <body>
            <h1>SalesForce Mock-up - parent window</h1>
            <p id="notification">JSON employees data received. Check console window.</p>
            <button id="btn">Send Emplyees to VueJS app</button>
            <iframe id="mainframe" src="http://localhost:8080/"></iframe>
        </body>
        
    </html>
    

    sales-force-mockup/main.js

    (function () {
    
      // DATA
      var employeesData = '[{"index":0,"age":20,"eyeColor":"blue","name":"Puckett Branch","gender":"male","company":"FIREWAX","email":"puckettbranch@firewax.com","phone":"+1 (968) 479-2314","salary":2356},{"index":1,"age":31,"eyeColor":"green","name":"Shelia Boyd","gender":"female","company":"LOCAZONE","email":"sheliaboyd@locazone.com","phone":"+1 (995) 468-3653","salary":6432}]';
    
      // SEND MESSAGE TO CHILD
        
      var btn = document.getElementById("btn");
    
      btn.addEventListener("click", () => {
        var frame = document.getElementById("mainframe");
        frame = frame ? frame.contentWindow : null;
    
        var message = {
          type: "employees-list",
          data: employeesData
        };
    
        frame.postMessage(message, "*");
      });
    
      // RECEIVE MESSAGE FROM CHILD
    
      window.addEventListener("message", (e) => {
    
        var data = e.data ? e.data : {};
        var type = data.type;
        var data = data.data;
    
        if (type === "employees-list") {
          document.querySelector("#notification").style.display = "block";
          console.log("Emplyees list received by PARENT: ", data);
        }
    
      });
    })();
    

    sales-force-mockup/main.css

    body {
        background: #4B9EE4;
        font-family: sans-serif;
        color: #fff;
    }
    h1{
        font-size: 22px;
        font-weight: 400;
        text-transform: uppercase;
    }
    iframe {
        width: 100%;
        height: 400px;
        border: 1px dashed #ccc;
    }
    button {
        padding: 10px;
        background: #a203b5;
        color: #fff;
        font-weight: bold;
        border: none;
        text-transform: uppercase;
        border-radius: 5px;
    }
    #notification {
        display: none;
        background: red;
        color: #fff;
        padding: 5px;
        border-radius: 3px;
        text-align: center;
    }
  8.  If you downloaded my VueJS demo application from our GitLab (not master branch) then just write in terminal window started in this project root directory:
    npm run install and after installation write there
    npm run serve to start application
    Then check the port on which app is running, and if needed, change this URL in sales-force-mockup/index.html in iframe src attribute
  9. When VueJS app is running, then go into sales-force-mockup folder and double click on index.htmlfile. Then VueJS external-frontend-app-for-salesforce app will be visible in iframe tag like on image below:

    SalesForce mockup development environment
    SalesForce mockup development environment
  10. You can play with that by clicking violet button to send data to child VueJS app, modify salaries there, and send data back to parent window and check it in console. It will look like on screen shot below:

    SalesForce mockup development environment
    SalesForce mockup development environment
  11. So what do we have now? We can simply continue development of our VueJS app (or react/angular/vanilla JS/whatever) and simulate the data which is comming back/to SalesForce based on our mockup static html page which is hosting the VueJS app. Native JavaScript postMessage communication API works almost the same as works LCC library, so we can simulate this in our local envorinment what makes for us much easier coding, debugging and testing.
  12. Check functioning example below:

The final solution

Here we will present the final solution how to communicate own custom made front-end JavaScript application (here in VueJS) with SalesForce hosting Aura (lightning) component.

If you are interested how we created own local development environment SalesForce instance mockup, then read this article from the beginning.

We base here on SalesForce Org (instance) project, created our another ARTICLE HERE.

Our JavaScript VueJS external-frontend-app-for-salesforce app connected via LCC library with SalesForce instance, which is descriebed in details below, is available HERE on GITLAB.

Whole SalesForce project with Aura component and JavaScript VueJS external-frontend-app-for-salesforce app’s distribution package as static resource is available HERE on GITLAB.

OK, let’s go step by step by elements of our VueJS app and SalesForce Aura component.

Custom JavaScript app with lightning-container LCC package

  1. First, we need to add to our project npm lightning-container package by npm install lightning-container command, and next import it into our project file by command (in ES2015+ syntax):
    import LCC from 'lightning-container'
  2. Next, we need to add methods for receiving data via LCC:
    LCC.addMessageHandler((data) => {
      let type = data.type;
      var content = data.data;
    
      if(type === "employees-list") {
        this.assignEmployeesData(content);
      }
    
    });
  3. And for sending data via LCC:
    const message = {
      type: type,
      data: data
    };
    LCC.sendMessage(message);
  4. In our VueJS application we are using also development environment simulating LCC by native js  window.postMessage() methods (read article from the beginning to learn that), so our code is a little bigger. The code of our VueJS app component responsible for the communication via LCC is HERE on GITLAB and is below:
    <template>
      <div id="app">
        <img alt="Vue logo" src="./assets/logo.png" class="logo">
        <h1>VueJS project for SalesForce</h1>
        
        <div v-if="employeesData.length">
          <h2>Employees list</h2>
          <button @click="sendData">Send data back</button>
          <table>
            <thead>
              <tr>
                <th>index</th>
                <th>age</th>
                <th>eyeColor</th>
                <th>name</th>
                <th>gender</th>
                <th>company</th>
                <th>email</th>
                <th>phone</th>
                <th>salary</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="row in employeesData" :key="row.index">
                <td>{{row.index}}</td>
                <td>{{row.age}}</td>
                <td>{{row.eyeColor}}</td>
                <td>{{row.name}}</td>
                <td>{{row.gender}}</td>
                <td>{{row.company}}</td>
                <td>{{row.email}}</td>
                <td>{{row.phone}}</td>
                <td><input type="number" v-model="row.salary"></td>
              </tr>
            </tbody>
          </table>
        </div>
    
      </div>
    </template>
    
    <script>
    import LCC from 'lightning-container';
    
    export default {
      name: 'App',
      data: function () {
        return {
          employeesData: []
        }
      },
      computed: {
        employeesDataHeaders () {
          const firstRow = this.employeesData.length ? this.employeesData[0] : {};
          return Object.keys(firstRow);
        }
      },
      methods: {
        sendData () {
          this.dispatchMessage("employees-list", this.employeesData);
        },
        assignEmployeesData (content) {
          this.employeesData = JSON.parse(content);
        },
    
        dispatchMessage (type, data) {
          const message = {
            type: type,
            data: data
          };
    
          if (process.env.NODE_ENV === 'production') {
            LCC.sendMessage(message);
          } else {
            const parentWindow = window.parent;
            parentWindow.postMessage(message, "*");
          }
        },
        receiveMessage () {
          if (process.env.NODE_ENV === 'production') {
    
          LCC.addMessageHandler((data) => {
            let type = data.type;
            var content = data.data;
    
            if(type === "employees-list") {
              this.assignEmployeesData(content);
            }
    
          });
    
          } else {
    
            window.addEventListener("message", (e) => {
    
              let data = e.data ? e.data : {};
              let type = data.type;
              var content = data.data;
              
              if(type === "employees-list") {
                this.assignEmployeesData(content);
              }
    
            });
    
          }
        }
      },
      mounted () {
        this.receiveMessage();
      }
    }
    </script>
    
    <style>
    body {
      background: #fff;
    }
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 10px;
    }
    
    table {
      width: 100%;
      max-width: 1200px;
      margin: 20px auto;
    }
    
    button {
        padding: 10px;
        background: #07ab85;
        color: #fff;
        font-weight: bold;
        border: none;
        text-transform: uppercase;
        border-radius: 5px;
    }
    
    .logo {
      max-width: 100px;
    }
    </style>
    

    So, if you didn’t read whole article – long story short – this component is responsible for:
    – take data from SalesForce with “Employees list”
    – display data in table
    – make salaries editable
    – send modified “Employees list” back to SalesForce
    – provide also development environemnet communication via native JS window.postMessage() instead LCC.

  5.  Next, like descriebed in our PREVIOUS ARTICLE HERE, we need to create a distribution package of VueJS app (with relative paths to scripts, so it must work by opening simply index.html file) and paste it into a staticresources folder (check on GITLAB):
    static resources
    static resources

    Then do right click on “staticresources” folder and click “Deploy Source to Org”:

    Embed front-end apps into SalesForce page
    Embed front-end apps into SalesForce page

    Embed front-end apps into SalesForce page
    Embed front-end apps into SalesForce page
  6. Next, we need to modify our Aura component from our PREVIOUS ARTICLE HERE, and after that, deploy to SalesForce Org (instance)myFrontendApp.cmp
    <aura:component implements="flexipage:availableForAllPageTypes" access="global" >
      <aura:attribute name="truthy" type="Boolean" default="false"/>
    
      <lightning:card title="VUE APP TEST">
          <aura:if isTrue="{!v.truthy}">
            Message received from child custom VueJS app. Check console window. 
          </aura:if>
          
          <br/>
    
          <lightning:button label="Send employees to custom VueJS app" onclick="{!c.sendMessage}"/>
          <lightning:container aura:id="vueApp" src="{!$Resource.external_frontend_app_for_salesforce + '/index.html'}"
            onmessage="{!c.handleMessage}" />
    
        </lightning:card>    
    </aura:component>

    We have here very basic code. Starting from the top:
    1. initialization of boolean value to show notification about received from VueJS app data
    2. Notification about received data
    3. Button to send hardocded JSON data with “Employees list”
    4. Lightning container hosting the VueJS app.

    myFrontendAppController.js

    ({
      sendMessage : function(component, event, helper) {
      
        var employeesData = '[{"index":0,"age":20,"eyeColor":"blue","name":"Puckett Branch","gender":"male","company":"FIREWAX","email":"puckettbranch@firewax.com","phone":"+1 (968) 479-2314","salary":2356},{"index":1,"age":31,"eyeColor":"green","name":"Shelia Boyd","gender":"female","company":"LOCAZONE","email":"sheliaboyd@locazone.com","phone":"+1 (995) 468-3653","salary":6432},{"index":2,"age":37,"eyeColor":"green","name":"Tameka Pacheco","gender":"female","company":"JUNIPOOR","email":"tamekapacheco@junipoor.com","phone":"+1 (840) 412-2533","salary":4678}]';
    
        var message = {
          type: "employees-list",
          data: employeesData
        };
        component.find("vueApp").message(message);
    
      },
    
      handleMessage: function(component, message, helper) {
        var data = JSON.parse(JSON.stringify(message.getParams().payload));
        var type = data.type;
        var data = data.data;
    
        if (type === "employees-list") {
          console.log("Emplyees list received by PARENT: ", data);
          component.set("v.truthy", true);
        }
      }
    })
    

    Here we have 2 methods only – to send and receive data from child VueJS app. It’s so basic that here is nothing to command, code is self-explained.

    Next, deploy the component:

    Embed front-end apps into SalesForce page
    Embed front-end apps into SalesForce page
  7. Next you must login into your SalesForce Org and add myFrontendApp component into a main page (details are in our PREVIOUS ARTICLE HERE)
    Embed front-end apps into SalesForce page
    Embed front-end apps into SalesForce page
    Embed front-end apps into SalesForce page
    Edit home page to add custom component

    Embed front-end apps into SalesForce page
    Edit home page to add custom component – drag&drop it
  8. Then just jumpt into you main SalesForce instance page and you can see results like on screen shot and gif film below:
    Embed front-end apps into SalesForce page
    Embed front-end apps into SalesForce page

    Gif film:

And here we have reached the end. I hope that I have described in details how the communication of external custom JavaScript app with SalesForce via LCC library looks like.