Embedding Oracle Analytics — Working with iFrames and Events

Introduction

Mike Durran
Oracle Developers
7 min readJul 28, 2023

--

Photo by Markus Spiske on Unsplash

An iFrame is a good way to sandbox embedded Oracle Analytics content, especially if the page where you are embedding uses React, Angular etc., since in these cases, the rendering mechanisms work differently to those used by Oracle Analytics (JET).

I’ve previously written about using iFrames to embed Oracle Analytics content. The previous blog describes using the iFrame parameters ‘src’ and ‘srcdoc’ in addition to describing a mechanism to get values into the iFrame using window.PostMessage(). But what about passing values out of the iFrame, such as events that are invoked from an Oracle Anlaytics canvas data action?

Passing values into and out of an iFrame

This blog from JavaScriptBit does a great job of describing the mechansims to pass values into and out of iFrames and I’ve used a similar approach in this blog to provide details of how to setup this ‘two-way’ value passing for embedded Oracle Analytics canvases.

The example from JavaScriptBit illustrates sending conent to and from an iFrame and I’ve illustrated that aspect here and extended it to pass a filter to an embedded analytics canvas then invoke a data action (called ‘Decision: Bonus Amount’) from the embedded canvas and pass that to the parent page:

Illustration of passing values into and out of iFrames

Here is the sample code specific to the embedded analytics aspect of this example. Firstly, the drop down to choose the value of the filter (‘yes’, ‘no’ or ‘both’) to pass into the iFrame.

    <div id="app">
<span>Select a value to apply a filter to 'Over Time' embedded in the iFrame:</span>
<select id="overTimeSelect" onchange="applyFilter()">
<option value="Yes">Yes
<option value="No">No
<option value="Yes,No">Both
</select>
</div>

On selction of a value, we execute a function applyFilter(). In this sample code, the iFrame is referenced by id = ‘oacembed’ and the sample is running on a localhost hence that is referenced in the postMessage parameters:

      function applyFilter() {
const overTimeValue = document.getElementById("overTimeSelect").value;
console.log(overTimeValue);
const overTimeValueArray = overTimeValue.split(",");
document.getElementById("oacembed").contentWindow.postMessage(overTimeValueArray, "http://localhost:8080");
}

Here is the entirety of the iFrame sample code using the srcdoc parameter. Since it is using the inline HTML approach with ‘srcdoc’, it is quite long, so I have included commments (// ** Comment **) within the code to explain what each section is doing:

    <iframe id='oacembed' style="margin-top:1em;width:100%;height:600px;border:solid 1px #ccc;border-radius:4px;overflow:hidden;"
srcdoc="
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />

// ** The link to the Oracle Analytics embedding.js file **
<script src='https://<OAC-INSTANCE>.ocp.oraclecloud.com/public/dv/v1/embedding/standalone/embedding.js' type='text/javascript'>
</script>

<title>Document</title>
</head>

<body>
<h3>The message posted from the outer page:</h3>
<div id='message'>Post a message from the outer page</div>

// ** The text input field for posting a message to the parent page **
<input id='messageText' style='margin-top: 1em;' type='text'/>
<button id='sendMessage'>Send Message to Outer Page</button>

// ** Post message out of the iFrame **
<script>
var button = document.getElementById('sendMessage');
button.addEventListener('click', function () {
var message = document.getElementById('messageText').value;
window.parent.postMessage(message, 'http://localhost:8080');
});
</script>

// ** The Oracle Analytics embedding tag with ko observables referenced with {{ }} **
<div id='mydiv' style='position: absolute; width: 100%; height: 500px'>
<oracle-dv id='myEmbed'
project-path='{{projectPath}}'
active-page='canvas'
active-tab-id='1'
filters='{{filters}}'>
</oracle-dv>
</div>

<script>
// ** The following contains the emebdding dependences and knockout bindings

requirejs(['knockout', 'obitech-application/application', 'ojs/ojcore', 'ojs/ojknockout', 'ojs/ojcomposite', 'jet-composites/oracle-dv/loader'],
function(ko, application) {

// ** definition of filter observable with initial values **
var filterArray = ko.observableArray([{
'sColFormula': 'XSA(\'<user>\'.\'attrition\').&quot;Columns&quot;.&quot;OverTime&quot;',
'sColName': 'OverTime',
'sOperator': 'in',
'isNumericCol': false,
'isDateCol': false,
'bIsDoubleColumn': false,
'aCodeValues': [],
'bHonorEmptyFilter': true,
'aDisplayValues': ['No','Yes']}
]);

function MyProject() {
var self = this;
self.projectPath = ko.observable('/@Catalog/shared/Embed/Decisions Integration/AttritionBonusDecision');
self.filters = filterArray;
}

ko.applyBindings(MyProject);

// ** Define a listener in the iFrame for messages posted into iFrame **
window.addEventListener('message', (event) => {

// ** If message is a string then display on the page **
if ((typeof event.data === 'string' || event.data instanceof String) && event.origin == 'http://localhost:8080') {
var msg = event.data;
document.getElementById('message').innerText = msg;
}

// ** If the message is an array, pass this to the Oracle Analytics filters observable **
if ((Array.isArray(event.data)) && event.origin == 'http://localhost:8080') {
var msg = event.data;
let self = this;
self.filters([{
'sColFormula': 'XSA(\'<user>\'.\'attrition\').&quot;Columns&quot;.&quot;OverTime&quot;',
'sColName': 'OverTime',
'sOperator': 'in',
'isNumericCol': false,
'isDateCol': false,
'bIsDoubleColumn': false,
'aCodeValues': [],
'bHonorEmptyFilter': true,
'aDisplayValues': event.data}
]);
document.getElementById('message').innerText = msg;
}

}, false);
});

// ** Create a listener within the iFrame for the event data action **
var eventName = 'oracle.bitech.dataaction';
var element = document.getElementById('mydiv');

if (element) {
var oEventListener = element.addEventListener(eventName, function (e) {
// ** This code outputs the event payload to the browser console for information **
console.log('***** Payload from DV ***** ');
console.log('eventName = ' + e.detail.eventName);
console.log('payload = ' + JSON.stringify(e.detail.payload));
console.log('***** Payload from DV end ***** ');
var res = ' Data Received from Embedded DV Content: ';

// ** Obtain the columns and values from the payload sent by the event data action **
// ** Add the values to a string that can be passed as a message to the parent page **
Object.keys(e.detail.payload.context).forEach(function(key) {
console.log(key, e.detail.payload.context[key]);
res = res.concat(key);
res = res.concat(' : ');
var temp = e.detail.payload.context[key]['oValueMap'];
var temp1 = Object.keys(temp)[0];
res = res.concat(temp1);
res = res.concat(' | ');
});
window.parent.postMessage(res, 'http://localhost:8080');
}, true);
}
</script>
</body>
</html>">
</iframe>

There is a message event listener defined on the parent page that detects the content sent from the iFrame postMessage and displays it on the page. Here are the sample code snippets :

      
// ** Define an area of the parent page to show message content from iFrame **
<h3>Messages sent from within the iFrame:</h3>
<div id="messageArea">No message from iFrame</div>

var messageArea = document.getElementById("messageArea");

//** Listener for messages on parent page **
window.addEventListener("message", onMessageHandler);

// ** Display message from iFrame on parent page if a string **
function onMessageHandler(event) {
if (typeof event.data === "string") {
messageArea.innerText = event.data;
}
}

Summary

In this blog, I’ve illustrated how to pass content into and out of iFrames that contain embedded Analytics content using the postMessage method.

References

Blog on embedding Oracle Analytics using iFrames
Oracle Analytics Doc on Events
JavaScriptBit blog on passing values between iFrames and parent pages.

Sample Code

I’ve included the entire sample code below — parameters that need to be replaced with your own values are indicated with <BRACKETS> these are:

  • Path to the emebdding.js file for your instance.
  • Path to the project being embedded (that includes an event data action).
  • The sColFormula for the column being filtered. Note that in the iFrame srcdoc, any double quotes need to be escaped with &quot; (the sample is filtering on a column ‘Over Time’.
  • The ‘http://localhost’ would need to be changed to your dev environment or production hosts when deployed.
<html>
<head>
<title>PostMessage Demo</title>
<meta charset="UTF-8" />
<style>
body {
font-family: sans-serif;
}
#app {
margin-bottom: 2em;
}

h2 {
margin-bottom: 0;
}
</style>
</head>

<body>

<div id="app">

<input id="message" type="text" />
<button id="sendMessage">Post Message to iFrame</button>
<h3>Messages sent from within the iFrame:</h3>
<div id="messageArea">No message from iFrame</div>

<br>

<span>Select a value to apply a filter to 'Over Time' embedded in the iFrame:</span>
<select id="overTimeSelect" onchange="applyFilter()">
<option value="Yes">Yes
<option value="No">No
<option value="Yes,No">Both
</select>

</div>

<script>

// Send from parent to child iFrame
var button = document.getElementById("sendMessage");
var messageArea = document.getElementById("messageArea");

function sendMessage() {
const message = document.getElementById("message").value;
const iframe = document.getElementById("oacembed");
document.getElementById("oacembed").contentWindow.postMessage(message, "http://localhost:8080");
}

button.addEventListener("click", sendMessage);

function applyFilter() {
const overTimeValue = document.getElementById("overTimeSelect").value;
console.log(overTimeValue);
const overTimeValueArray = overTimeValue.split(",");
document.getElementById("oacembed").contentWindow.postMessage(overTimeValueArray, "http://localhost:8080");
}

// Send information from child to parent.

window.addEventListener("message", onMessageHandler);

function onMessageHandler(event) {
if (typeof event.data === "string") {
messageArea.innerText = event.data;
}
}

</script>

<h2>iframe with embedded Oracle Analytics canvas:</h2>
<iframe id='oacembed' style="margin-top:1em;width:100%;height:600px;border:solid 1px #ccc;border-radius:4px;overflow:hidden;"
srcdoc="
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<script src='https://<OAC-INSTANCE>.analytics.ocp.oraclecloud.com/public/dv/v1/embedding/standalone/embedding.js' type='text/javascript'>
</script>
<title>Document</title>
</head>

<body>
<h3>The message posted from the outer page:</h3>
<div id='message'>Post a message from the outer page</div>

<input id='messageText' style='margin-top: 1em;' type='text'/>
<button id='sendMessage'>Send Message to Outer Page</button>

<script>
var button = document.getElementById('sendMessage');
button.addEventListener('click', function () {
var message = document.getElementById('messageText').value;
window.parent.postMessage(message, 'http://localhost:8080');
});
</script>

<div id='mydiv' style='position: absolute; width: 100%; height: 500px'>
<oracle-dv id='myEmbed'
project-path='{{projectPath}}'
active-page='canvas'
active-tab-id='1'
filters='{{filters}}'>
</oracle-dv>
</div>

<script>

requirejs(['knockout', 'obitech-application/application', 'ojs/ojcore', 'ojs/ojknockout', 'ojs/ojcomposite', 'jet-composites/oracle-dv/loader'],
function(ko, application) {

var filterArray = ko.observableArray([{
'sColFormula': 'XSA(\'<USER>\'.\'attrition\').&quot;Columns&quot;.&quot;OverTime&quot;',
'sColName': 'OverTime',
'sOperator': 'in',
'isNumericCol': false,
'isDateCol': false,
'bIsDoubleColumn': false,
'aCodeValues': [],
'bHonorEmptyFilter': true,
'aDisplayValues': ['No','Yes']}
]);

function MyProject() {
var self = this;
self.projectPath = ko.observable('<PATH TO YOUR PROJECT>');
self.filters = filterArray;
}

ko.applyBindings(MyProject);

window.addEventListener('message', (event) => {

if ((typeof event.data === 'string' || event.data instanceof String) && event.origin == 'http://localhost:8080') {
var msg = event.data;
document.getElementById('message').innerText = msg;
}


if ((Array.isArray(event.data)) && event.origin == 'http://localhost:8080') {
var msg = event.data;
let self = this;
self.filters([{
'sColFormula': 'XSA(\'<USER>\'.\'attrition\').&quot;Columns&quot;.&quot;OverTime&quot;',
'sColName': 'OverTime',
'sOperator': 'in',
'isNumericCol': false,
'isDateCol': false,
'bIsDoubleColumn': false,
'aCodeValues': [],
'bHonorEmptyFilter': true,
'aDisplayValues': event.data}
]);
document.getElementById('message').innerText = msg;
}

}, false);

});

var eventName = 'oracle.bitech.dataaction';
var element = document.getElementById('mydiv');

if (element) {
var oEventListener = element.addEventListener(eventName, function (e) {
console.log('***** Payload from DV ***** ');
console.log('eventName = ' + e.detail.eventName);
console.log('payload = ' + JSON.stringify(e.detail.payload));
console.log('***** Payload from DV end ***** ');
var res = ' Data Received from Embedded DV Content: ';

Object.keys(e.detail.payload.context).forEach(function(key) {
console.log(key, e.detail.payload.context[key]);
res = res.concat(key);
res = res.concat(' : ');
var temp = e.detail.payload.context[key]['oValueMap'];
var temp1 = Object.keys(temp)[0];
res = res.concat(temp1);
res = res.concat(' | ');
});
window.parent.postMessage(res, 'http://localhost:8080');
<!-- document.getElementById('response_div').innerHTML = res; -->
}, true);
}

</script>
</body>
</html>">
</iframe>
</body>
</html>

--

--

Mike Durran
Oracle Developers

Analytics Product Manager at Oracle. [All content and opinions are my own]