# Guide: HTML & JourneyApps  iFrame Client

This article provides best practices and guides on how to import interactive custom HTML iFrames into the JourneyApps runtime using the [JourneyApps iFrame Client](https://github.com/journeyapps/journey-iframe-client) and the [`html`](https://docs.journeyapps.com/reference/build/ui-components/all-ui-components/html)  UI component. &#x20;

The JourneyApps iFrame Client library is used inside an HTML file and allows your JourneyApps application and the HTML file [to communicate with one another](https://docs.journeyapps.com/reference/build/ui-components/all-ui-components/html-advanced-topics#communicating-with-the-html-component). This communication is achieved through combining the following methods in the HTML file and the application's JS/TS.

* [`post()`](https://docs.journeyapps.com/reference/build/ui-components/all-ui-components/html/..#post) /  [`postNonBlocking()`](https://docs.journeyapps.com/reference/build/ui-components/all-ui-components/html/..#postnonblocking)
* [`on()`](https://docs.journeyapps.com/reference/build/ui-components/all-ui-components/html/..#on)

### Example Overview

In the two examples below, we’ll display a line chart using [Chart.js](https://www.chartjs.org/) containing a count of the US population by year, which is stored in your app's DB. Each of these DB objects will have `count`, `nation`, and `year` fields. We’ll then render the count by year for the United States in an `html` component.

### Data Model & Sample Data

Follow the steps below to set up the data model and fetch the population data which will seed the JourneyApps DB with sample data.

Update your Data Model and include the following model:

{% code title="schema.xml" %}

```xml
<model name="population" label="Population">
    <field name="nation" label="Nation" type="text" />
    <field name="year" label="Year" type="text" />
    <field name="count" label="Count" type="number" />
      
    <display>{nation} {year}</display>
</model>
```

{% endcode %}

We'll use the [Data USA API](https://datausa.io/about/api/) to get the population count by year and nation, then save this to `DB` objects in JourneyApps. Each population object will contain a nation, year, and population count. Start by creating a function called `getData` in our `main.js`:

{% code title="main.js" %}

```javascript
function getData() {
   var url = "https://datausa.io/api/data?drilldowns=Nation&measures=Population";
   var options = {
       method: "GET",
       headers: {
           "Content-Type": "application/json"
       }
   };

   return fetch(url, options)
       .then(function (response) {
           if (response.status < 200 || response.status >= 300) {
               // Request failed
               throw new Error("Failed to make network request: [" + response.status + "]");
           } else {
               return response.json();
           }
       }).then(function (json) {
           var populationArrary = json.data;
           console.log(JSON.stringify(populationArrary, null, 2));
           for(var i = 0; i < populationArrary.length; i++) {
               notification.info("Syncing " + (i + 1) + "/" + populationArrary.length);
               var population = DB.population.first("nation = ? and year = ?", populationArrary[i].Nation, populationArrary[i].Year);
               if(!population) {
                   population = DB.population.create();
                   population.year = populationArrary[i].Year;
                   population.nation = populationArrary[i].Nation;
                   population.count = populationArrary[i].Population;
                   population.save();
               }
           }
           return notification.info("Request completed");
       }).caught(function (error) {
           dialog("Error fetching population data", error.message);
       });
}
```

{% endcode %}

Call the getData function in the `init()` function of your `main.js`:

{% code title="main.js" %}

```javascript
function init() {
    getData();
}
```

{% endcode %}

### Basic Example

In this basic example, we’ll create a single HTML file and upload this file into OXIDE for further editing.

Create a new HTML file e.g. *chart.html* using the following template:

```html
<!DOCTYPE html>
<html lang="en" style="height: 100%;">

    <head>
        <meta charset="UTF-8" />
        <title>File</title>
        <!--Reference to the journey-iframe-client, must have in the file-->
        <script src="https://cdn.jsdelivr.net/npm/journey-iframe-client@0.2.0/dist/bundle.min.js"></script>
        <!--Here is the reference to chart.js -->
        <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.bundle.min.js"></script>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0" />
    </head>
    
    <body>
    
    </body>

</html>
```

Notice that we added two script elements, one includes the [JourneyApps iFrame Client](https://github.com/journeyapps/journey-iframe-client) and the other includes ChartJS. This is the easiest way to reference the iFrame client in your HTML file.

Upload this file to OXIDE by navigating to the **Assets** workspace and dragging/dropping the *chart.html* file created in the previous step into the Assets/html directory as shown on the left-hand side of the Assets panel.

![](https://2865107717-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9TCHLR67eLHBOjPvHhud%2Fuploads%2Ft4ngXncvrWu5U9c5Tl1B%2Foxide-assets-html.png?alt=media\&token=b4555ac3-dbd7-4292-8a81-16ddd57858eb)

Next,  the uploaded file in OXIDE and update the `body` element of the file to include the following:

{% code title="chart.html" %}

```html
<body style="height: 100%;">
  <style type="text/css">
      .container {
          display: flex;
          flex-direction: row;
          flex-wrap: wrap;
          justify-content: space-evenly;
      }

      .chart-container {
          position: relative;
          margin: auto;
          height: auto;
          width: max(400px, 30vw);
      }
  </style>
  <h3 id="status" style="font-family: monospace;"> Loading Chart </h3>
  <div class="container">
      <div class="chart-container">
          <canvas id="chart_canvas" width="600" height="600"></canvas>
      </div>
  </div>
  <script>
       var client = new JourneyIFrameClient();
       window.addEventListener('DOMContentLoaded', function () {
           client.post("mounted", true)
               .then(function (result) {
                   if(result == "success") {
                       var status = document.getElementById('status');
                       status.remove();
                   }
               });

           client.on("render", function (chartConfig) {
               var ctx = document.getElementById("chart_canvas");
               var chart = new Chart(ctx, chartConfig)
           });
       });
  </script>
</body>
```

{% endcode %}

This update to the `body` includes the following changes:

* We added a few style attributes for our chart containers
* Added an `h3` element that displays "Loading Chart", but this gets removed when the JourneyApps Container returns a successful response
* We defined two `div` elements. One will act as a container, and another will hold our canvas element. This is where the ChartJS chart will be rendered when the JourneyApps Runtime posts the population data to the iFrame.
* We create a new script element that initializes the JourneyApps iFrame Client (i.e. `JourneyIFrameClient`)
* The script also includes an event listener that fires when the HTML has completely loaded ([DOMContentLoaded](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event)). This will post a message to the JourneyApps runtime letting it know it’s ready and can start accepting data from the JourneyApps runtime
  * This is done by the following: `client.post("mounted", true)`, which sends a message from the HTML file to the app JS runtime.
  * The app JS listens for messages on “mounted” with a `on(“mounted”)` method definition and returns true once it receives a message on this ‘channel’
* Finally, the script includes a JourneyIFrameClient event listener in the HTML file. This tells `JourneyIFrameClient` to wait for the JourneyApps Runtime to post data to the iFrame. In this case, it will post to the ChartJS configuration object. This is done with the `client.on("render")` definition.

Now that we have an event listener waiting to receive the ChartJS configuration, we will update the `main` view’s JS in OXIDE to post data to the JourneyApps iFrame Client. Let’s start by adding the chart config above the `init()` function as a global view variable:

{% code title="main.js" %}

```javascript
var chartConfig = {
   type: 'line',
   data: {
       labels: [],
       datasets: [{
           label: 'US Population',
           data: [],
           backgroundColor: [
               'rgba(255, 99, 132, 0.2)',
               'rgba(54, 162, 235, 0.2)',
               'rgba(255, 206, 86, 0.2)',
               'rgba(75, 192, 192, 0.2)',
               'rgba(153, 102, 255, 0.2)',
               'rgba(255, 159, 64, 0.2)'
           ],
           borderColor: [
               'rgba(255, 99, 132, 1)',
               'rgba(54, 162, 235, 1)',
               'rgba(255, 206, 86, 1)',
               'rgba(75, 192, 192, 1)',
               'rgba(153, 102, 255, 1)',
               'rgba(255, 159, 64, 1)'
           ],
           borderWidth: 1
       }]
   },
   options: {
       scales: {
           yAxes: [{
               ticks: {
                   beginAtZero: true
               }
           }]
       }
   }
};
```

{% endcode %}

Next, we will update the `init()` function to include the following:

{% code title="main.js" %}

```javascript
function init() {
   component.html({ id: "frame" }).on("mounted", function (bool) {
       updateConfig();
       component.html({ id: "frame" }).post("render", chartConfig);
       return "success";
   });
}
```

{% endcode %}

The update to the `init()` function includes the following changes:

* First, we add an `on` event that listens for the mounted event from the iFrame. Without the event listener, it is not possible to post to the JourneyApps iFrame Client before it has completed the `DOMContentLoaded` event. In this case, the JourneyApps Runtime will not recognize the iFrame and will fail to post.
* Once the mounted event is invoked we execute the `updateConfig` function, which queries the database and updates the config. We’ll add this in the next step.
* Once that function has completed we post the `chartConfig` object to the render event that is in the iFrame.
* We then return success to let the iFrame know that the job is completed, which then removes the "Loading Chart" `h3` element.

Next, add the `updateConfig` function in your `main` view below the `init()` function. This function is responsible for retrieving the population data from the `DB` and updating the `chartConfig` with the correct labels and data to display in the chart:

{% code title="main.js" %}

```javascript
function updateConfig () {
   var populationData = DB.population.all().orderBy("year").toArray();
   var labels = populationData.map(function (population) {
       return population.year;
   });
   var stock_levels = populationData.map(function (population) {
       return population.count;
   });
   chartConfig.data.labels = labels;
   chartConfig.data.datasets[0].data = stock_levels;
}  
```

{% endcode %}

Finally, we will add the [`html`](https://docs.journeyapps.com/reference/build/ui-components/all-ui-components/html) UI component to our `main` view XML to display the iFrame:

{% code title="main.view\.xml" %}

```xml
<view title="US Population">
    <html id="frame" src="html/chart.html" show-fullscreen-button="true"/>
</view>
```

{% endcode %}

Notice that we added an [`id`](https://docs.journeyapps.com/reference/build/ui-components/xml-fields/id) attribute with a value of `frame`. This can be used when you have multiple HTML iFrames and need to target specific `post` or `on` events. We also reference this `id` when targeting the component in the runtime using `component.html({ id: "frame" })`. Also see this [component methods](https://docs.journeyapps.com/reference/build/ui-components/all-ui-components/html/..#component-methods) section.<br>

Deploy your app and test the result on your device. The result should look like this:<br>

![](https://2865107717-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9TCHLR67eLHBOjPvHhud%2Fuploads%2FIBofwFfFTxHJGnXS331C%2Fhtml-iframe-guide-result.png?alt=media\&token=e62ebf2e-b356-49d7-871e-b18875193410)

{% hint style="info" %}
**Tip**: To customize the chart, update the `chartConfig` based on options available on [chart.js](https://www.chartjs.org/).
{% endhint %}

### Advanced Example

In this example, similar to the [Basic Example](#basic-example) above, we’ll be creating the HTML file locally and will then upload it to a JourneyApps application. However, all of the development of the iFrame will be done locally in this advanced guide. We achieve this by defining an HTML file and injecting compiled JavaScript into the file. This is especially helpful when developing more advanced iFrame clients.

#### Prerequisites <a href="#prerequisites-5" id="prerequisites-5"></a>

* **Node.js**: You can install the latest version directly from the [Node.js](https://nodejs.org/en/) site, or install the latest version using [nvm](https://github.com/nvm-sh/nvm).
* **Yarn**: You can install the latest version of yarn [here](https://yarnpkg.com/).
* Complete the [Data Model & Sample Data](#data-model-and-sample-data) section of this guide to configure and populate the data used in this example.

#### Setup <a href="#setup-6" id="setup-6"></a>

Create a new directory on your machine, initialize a Node.js application and create the basic file/folder structure for the project.

```
> mkdir chart
> cd chart
> yarn init
```

Install the [journey-iframe-client](https://github.com/journeyapps/journey-iframe-client) using yarn

`yarn add journey-iframe-client`

Create the following file/folder structure

![](https://aws1.discourse-cdn.com/business7/uploads/journeyapps/original/1X/f007763f6b0bd4b78e04307a6ff19ec9572438d5.png)

In this example, we are not going to write the JavaScript in our HTML as we did in the basic example. Instead, we are going to write our JS in the `index.js` file and use `webpack` to compile our JS and HTML files into a single file, which we will upload to OXIDE.

Update your `package.json` file and add the following as a script node:

{% code title="package.json" %}

```json
"scripts": {
   "build": "webpack"
}
```

{% endcode %}

Update your `package.json` with the following to your `devDependencies`:

{% code title="package.json" %}

```json
"devDependencies": {
    "@types/node": "^10.11.6",
    "css-loader": "^1.0.0",
    "html-webpack-inline-source-plugin": "^0.0.10",
    "html-webpack-plugin": "^3.2.0",
    "node-sass": "^4.9.3",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "superagent": "^4.0.0-beta.5",
    "ts-loader": "^5.2.1",
    "typescript": "^3.1.1",
    "webpack": "^4.20.2",
    "webpack-cli": "^3.1.2",
    "yaml": "^1.0.0"
}
```

{% endcode %}

Run `yarn install` to update your node modules and install all the `devDependencies`

Then, dd the following to your `webpack.config.js` file:

{% code title="webpack.config.js" %}

```javascript
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
const yaml = require('yaml');
const fs = require('fs');

const config = yaml.parse(fs.readFileSync('config.yml', 'utf8'));
const {outputFileName, htmlTitle} = config;

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: htmlTitle,
            template: './src/template.html',
            filename: outputFileName,
            inlineSource: '.(js css ts)$'
        }),
        new HtmlWebpackInlineSourcePlugin()
    ],
    resolve: {
        extensions: ['.ts', '.tsx', '.js']
    },
    module: {
        rules: [
            { test: /\.tsx?$/, loader: 'ts-loader' },
            {
                test: /\.scss$/,
                use: [
                    "style-loader", // creates style nodes from JS strings
                    "css-loader", // translates CSS into CommonJS
                    "sass-loader" // compiles Sass to CSS, using Node Sass by default
                ]
            }
        ]
    }
};
```

{% endcode %}

You’ll also notice that we added rules to our `webpack` config. These will allow us to style our iFrame using SASS and we’ve added rules for TypeScript.

Add the following to your `config.yml` file:

{% code title="config.yml" %}

```yaml
outputFileName: output.html
htmlTitle: Template name
```

{% endcode %}

Replace the values of the `outputFileName` and `htmlTitle` as you see fit. These are used by `webpack` when the file is compiled and sets the output HTML filename as well as the name of the title of the HTML file.

#### Development <a href="#development-7" id="development-7"></a>

Open the `src/template.html` file and add the following:

{% code title="template.html" %}

```html
<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.bundle.min.js"></script>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0" />

        <title>
            <%= htmlWebpackPlugin.options.title %>
        </title>
    </head>

    <body>

    </body>
</html>
```

{% endcode %}

If you want to store this source code in a remote repository, please make sure you update your `.gitignore` file accordingly. Here is an example:

{% code title=".gitignore" %}

```
dist
node_modules
.DS_Store
```

{% endcode %}

You won’t need to push the `dist` or `node_modules` files to your remote repository.

Update the `body` element of the `src/template.html` with the following:

<pre class="language-html" data-title="template.html"><code class="lang-html"><strong>&#x3C;body>
</strong>    &#x3C;h3 id="status" style="font-family: monospace;"> Loading Chart &#x3C;/h3>
    &#x3C;div class="container">
        &#x3C;div class="chart-container">
            &#x3C;canvas id="chart_canvas" width="600" height="600">&#x3C;/canvas>
        &#x3C;/div>
    &#x3C;/div>
&#x3C;/body>
</code></pre>

Update the `src/index.js` with the following:

{% code title="index.js" %}

```javascript
const JourneyIFrameClient = require('journey-iframe-client');
const client = new JourneyIFrameClient();

window.addEventListener('DOMContentLoaded', function () {
    this.console.log('Window has loaded, posting mounted event');
    client.post("mounted", true)
        .then(function (result) {
            if(result == "success") {
                var status = document.getElementById('status');
                status.remove();
            }
        });

    client.on("render", function (chartConfig) {
        var ctx = document.getElementById("chart_canvas");
        var chart = new Chart(ctx, chartConfig);
    });
});
```

{% endcode %}

Similar to our Basic Example, we’ll add an event listener that fires when `DOMContentLoaded` occurs. This will post to the JourneyApps runtime informing it that it has loaded and is ready to accept messages.

Add a few styles to your `src/sass/styles.scss`:

{% code title="styles.scss" %}

```scss
body {
    height: 100%;
}

.container {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: space-evenly;
}

.chart-container {
    position: relative;
    margin: auto;
    height: auto;
    width: max(400px, 30vw);
}
```

{% endcode %}

1. Run the `yarn` build command in the terminal. This will run the `webpack` command and compile all the JS files, SASS files and inject them into the `template.html` file. The result is found in the `dist/output.html` (or whatever you specified as the value for the `outputFileName` key in the `config.yml` file).
2. Upload the compiled HTML file to OXIDE under the **Assets** workspace within the **/html** directory.
3. **Tip**: Avoid opening the compiled HTML file on OXIDE. This file is the compiled version of your iFrame client and should not be edited in its compiled form.
4. Follow steps 3 - 7 in the Basic Example of this post. These steps will guide you through the process of adding the HTML component to the view XML, retrieving the sample population data from the database, and finally posting the data to the JourneyApps iFrame Client, which will render the chart and display the data. Please note that if you updated the `outputFileName` key in the `config.yml` file, make sure you update the src attribute of the JourneyApps HTML component to reference the correct HTML file uploaded to OXIDE.
5. Finally, test the result in your app to ensure the data is showing correctly.

The complete source code of this HTML iFrame can be found [here](https://github.com/journeyapps-customer-success/html-chart-js-template).
