Build a view for your Adobe Launch extension
Tags: javascript, tagging, adobelaunch
Published: 2021-12-28 14:12:29

> This post is part of a series about auto-tagging

In this article I will describe how to create your views for your Adobe Launch extension. Extensions are a way to extend your tagging implementation to provide reusable content for your tagging team to use in all your Adobe Launch containers.

When building a private or public extension, you will be given the option to provide a configuration view for your extension or extension feature. While it is optional to provide a configuration, I will advise you to always choose this option.

As a best practice it is advised to add a view when creating a data element or a condition or a rule or an extension. Even if you are not planning to return a `settings` object, a view should be used to provide additional details about the feature.

Going forward I will consider that you have already ran the following command and answered yes to the question about the configuration view. 

```
npx @adobe/reactor-scaffold
```

## What does the scaffold command do

When we first generate the scaffold of our extension you would notice that the `src` folder is automatically generated for us. Inside this folder you will find the `lib` and `view` folder.

The `view` folder should contain all the HTML files which will be used in the Adobe Launch UI. When an extension is installed, Adobe Launch will allow you to use the actions, data elements etc... that you configured in your `extension.json` file. When you select in the Adobe Launch interface one of the features that you configured, Adobe Launch will fetch the corresponding HTML file and load it in an iframe. The main purpose of a view is to return a `settings` object that will then be used in the JavaScript file present in the `lib` folder.

All files in the `lib` folder will be present in the Adobe Launch core library that you load on your website.

> Try to remove the `.min` from your Adobe Launch library URL and have a look. You will see the content of your js files inside it.

It is important to note that for the views to work you need to put the CSS files and any other helper files inside the `view` folder itself. This way when you will use `npx @adobe/reactor-packager` command, they will automatically be zipped.

## Building our view.

By default, the scaffold command will not add any CSS to your HTML file. It is up to you to use any CSS framework that suits you best (hence the great flexibility of the extensions). This means that you can customize your extension to match your brand design. 

> Please note that while some public extensions available in Adobe Launch use Single Page Application framework like React to create the Views, you do not have to use any. I use default HTML, CSS and Vanilla JavaScript and it meets all my requirements.

The default HTML code will look as follow in your HTML files:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Extension Configuration</title>
  </head>
  <body>
    Extension Configuration Template

    <script src="https://assets.adobedtm.com/activation/reactor/extensionbridge/extensionbridge.min.js"></script>
    <script>
      window.extensionBridge.register({
        init: function(info) {
          if (info.settings) {
            // TODO Populate form values from persisted settings.
          }
        },

        getSettings: function() {
          // TODO Return settings object from form values.
        },

        validate: function() {
          // TODO Return whether settings are valid.
        }
      });
    </script>
  </body>
</html>
```

In the example above, you will see `Extension Configuration Template`. This section can be replaced by any HTML code that you want. You can style as you want as well. Usually, you will place your configuration form in this section.

You will see 2 script tags. Do not delete them. The first one is about the extensionbridge and needs to be present.

In the next script tag, you will find:

**validate**

In this section you will need to create your logic to validate that the configuration values provided match the minimum requirements. You need to check if required fields have a value and if specific fields contain right values. You should always return a Boolean. `true` should be returned if validation is a success and `false` should be returned if validation is a failure.

```javascript
validate: function() {
    return true;
}
```

**getSettings**

This function should be used to return the `settings` object which contains the configuration that will be used in the js file for the rule or data element or condition or extension. 

```javascript
getSettings: function() {
    return {
        test1: getValueFromField1(),
        test2: 123,
        test3: [1, 3]
    }
},
```

You should not add any validation logic inside the `getSettings` section. The `validate` section should be used instead. Also, you should always have corresponding validation logic inside your js file to make sure the `settings` object is as you would expect. 

**init**

This is called when the view is loaded. It checks if an existing `settings` object exist. If it exists, you can then prepopulate the form with the existing configuration.

```javascript
init: function(info) {
    if (info.settings) {
        if (info.settings.test1) {
        	test1Field.value = info.settings.test1;
        }
    }
},
```

If your configuration view should not return any settings then you can replace it by this

```javascript
window.extensionBridge.register({
    init: function(info) {},

    getSettings: function() {
        return {};
    },

    validate: function() {
        return true;
    }
});
```

## Add your own CSS and helpers for the view

To upload an extension, you will first need to package it. Luckily for us Adobe already provides a command for this

```
npx @adobe/reactor-packager
```

For our CSS and helper JavaScript files to be part of the packager output zip file, we will need to reference them in the `view` folder. If they are outside there will not be available when the view HTML files loads in Adobe Launch.

You can also use CDN to deliver your helpers and CSS files. As the HTML file is loaded in an iframe, it should be safe to consider that any risks related to CDN should be mitigated.

For our example we will add Bulma CSS framework, ION Icons and some custom JavaScript helper files.

1. Create the `css` folder in the `view` folder
2. Create the `js` folder in the `view` folder
3. Create the `helpers` folder in the `js` folder
4. Go to [Bulma](https://bulma.io/documentation/overview/start/) website and download the latest build. Place the bulma.min.css in the css folder
5. Add the file `viewHelpers.js` inside the `js/helpers` folder
    ```javascript
        //Support selection of data element
        function addListenerToDataElement() {
            var dataElementSelectorElements = document.querySelectorAll('.data-element');

            if (dataElementSelectorElements) {
                dataElementSelectorElements.forEach(function (element) {
                    element.addEventListener('click', function (event) {
                        window.extensionBridge.openDataElementSelector().then(function (dataElement) {
                            var target = event.target;
                            while (!target.classList.contains('field')) {
                                target = target.parentElement;
                            }
                            target.querySelector('.input').value = dataElement;
                        });
                    });
                })
            }
        }

        addListenerToDataElement();

        //used when missing field
        function toggleNotification() {
            document.querySelector('#error').classList.toggle('is-hidden');
        }
    ```
    
    The folder structure should now look as follow
    ```
        > src
            > view
                > actions
                > dataElements
                > configuration
                > events
                > css
                    bulma.min.js
                > js
                    > helpers
                        viewHelpers.js
    ```
6. Go to your html files and update them to reference your CSS file and JavaScript files
    ```html
    <!doctype html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <title>Extension Configuration</title>
    <link rel="stylesheet" href="../css/bulma.min.css">
    <script src="https://unpkg.com/ionicons@4.5.10-0/dist/ionicons.js"></script>
    </head>

    <body>
        <section class="section is-hidden" id="error">
            <div class="notification is-danger is-light">
                <button class="delete" onclick="toggleNotification()"></button>
                <ion-icon name="alert" class="has-text-danger"></ion-icon> Error message
            </div>
        </section>


        <script src="https://assets.adobedtm.com/activation/reactor/extensionbridge/extensionbridge.min.js"></script>
        <script>
            window.extensionBridge.register({
            init: function (info) { },

            getSettings: function () {
                return {};
            },

            validate: function () {
                return true;
            }
            });
        </script>
        <script src="../js/helpers/viewHelpers.js"></script>
    </body>

    </html>
    ```

## Add a form to configure our extension, action, data element or condition

The view HTML file will allow you to return a `settings` object with the configuration to be used when the action or data element or condition or extension is executed.

There is different ways to provide `settings`: 
* you can use input fields to provide hard coded values
* you can use data element selector to return a data element reference. On runtime the data element will be evaluated, and correct value will be returned. You will notice that the data element value needs to be return between `%` as `%myDataElement%`
* use a code editor to return a JSON object
* use a code editor to return JavaScript code to be executed as part of a function call (requires transform)

We are now going to add to our HTML file the structure of our form.

> We will use the `openDataElementSelector` and `openCodeEditor` shared views to edit our code.

```html
<!doctype html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Extension Configuration</title>
  <link rel="stylesheet" href="../css/bulma.min.css">
  <script src="https://unpkg.com/ionicons@4.5.10-0/dist/ionicons.js"></script>
</head>

<body>
  <section class="section is-hidden" id="error">
    <div class="notification is-danger is-light">
      <button class="delete" onclick="toggleNotification()"></button>
      <ion-icon name="alert" class="has-text-danger"></ion-icon> Error message
    </div>
  </section>

  <section>
    <!--Normal field-->
    <label class="label">Normal Field</label>
    <div class="field">
      <div class="control is-expanded">
        <input id="normal" class="input" type="text" placeholder="Input a value">
      </div>
    </div>
    <!--Field with Data Element selection-->
    <label class="label">Data Element Selection field</label>
    <div class="field has-addons">
      <div class="control is-expanded">
        <input id="dataElement" class="input" type="text" placeholder="Click on icon to select your data element">
      </div>
      <div class="control">
        <a class="button is-info data-element">
          <span class="icon is-small">
            <ion-icon name="reorder"></ion-icon>
          </span>
        </a>
      </div>
    </div>
    <!--Button to edit JSON config-->
    <button class="button is-medium is-outlined is-info" id="edit-config" onClick="editConfig()">
      <span class="icon is-large is-primary">
        <ion-icon name="code"></ion-icon>
      </span>
      <span>Edit Config</span>
    </button>
    <!--Button to edit custom code-->
    <button class="button is-medium is-outlined is-info" id="edit-function" onClick="editFunction()">
      <span class="icon is-large is-primary">
        <ion-icon name="code"></ion-icon>
      </span>
      <span>Edit Function</span>
    </button>
  </section>

  <script>
    /**
     * Will open a code editor to edit the JSON value
     */
    function editConfig() {
      window.extensionBridge.openCodeEditor({
        code: window.config.source,
        language: 'json'
      }).then(function (code) {
        window.config.source = code;
      });
    }

    /**
     * Will open a code editor to edit the custom code
     */ 
    function editFunction() {
      window.extensionBridge.openCodeEditor({
        code: window.customCode.source,
        language: 'javascript'
      }).then(function (code) {
        window.customCode.source = code;
      });
    }
  </script>


  <script src="https://assets.adobedtm.com/activation/reactor/extensionbridge/extensionbridge.min.js"></script>
  <script>
    window.customCode = { source: '' } //Stores custom code to be displayed in code editor
    window.config = { source: '' } //Stores custom JSON config to be displayed in code editor
    var normal = document.querySelector('#normal'); 
    var dataElement = document.querySelector('#dataElement');

    window.extensionBridge.register({
      // We need to remember to prepopulate the config forms so we can edit saved data
      init: function (info) {
        if (info.settings) {
          if (info.settings.normal) {
            normal.value = info.settings.normal;
          }

          if (info.settings.dataElement) {
            dataElement.value = info.settings.dataElement;
          }

          if (info.settings.config) {
            window.config.source = info.settings.config;
          }

          if (info.settings.customCode) {
            window.customCode.source = info.settings.customCode;
          }
        }
      },

      //We will return a custom object that will be available in the settings object during runtime
      getSettings: function () {
        return {
          normal: normal.value,
          dataElement: dataElement.value,
          config: window.config.source,
          customCode: window.customCode.source
        };
      },

      validate: function () {
        return true;
      }
    });
  </script>
  <script src="../js/helpers/viewHelpers.js"></script>
</body>

</html>
```

For the function part, we will also need to specify the property `tranforms` in the `extension.json` file. In our case we want our custom code to be stored in a function so that we can execute it. By default, any custom code using the code editor will be stored as a String in the `settings` object.

Inside extension.json add `transforms` property for the view:
```json
"configuration": {
    "viewPath": "configuration/configuration.html",
    "schema": {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "type": "object",
        "properties": {}
    },
    "transforms": [{
        "type": "function",
        "propertyPath": "customCode",
        "parameters": ["window", "anotherArgument", "anotherOne"]
    }]
},
```
> Notice that you can specify more than one transform action. In our case we are using a type of `function` on the settings property `customCode`. We also specify that the function can have 3 arguments when it is called.

Once the extension is uploaded, navigate to the extension or rule or data element or condition that you configured previously. You should see the form that you created. When you save the changes, the `settings` object will be saved when the main Adobe Launch library has been built.

> Check the Adobe Launch library URL without the .min and you should see how the settings object is saved.

## Use the settings object in the corresponding JavaScript file

When you create an action or data element or condition or extension configuration view using the scaffold command, you will notice that:
* it creates an HTML file in the corresponding folder type of the `view` folder (i.e: an action will be saved in the `actions` folder)
* it creates a JavaScript file in the corresponding folder type in the `lib` folder (i.e: an action will be saved in the `actions` folder). The file will have the same name but with an extension of `.js`
* it updates the `extension.json` that tells which view to use and which corresponding js file to use.

> Notice that by default the configuration view for the extension will not generate a js file. You will need to create one and reference it in the `extension.js` file in the `main` property.

In the js file you will see that it would have generated some skeleton code.

```javascript
'use strict';

module.exports = function(settings) {
  // TODO Perform some action.
};
```

> Conditions also have a `trigger` argument.

Please make sure to always update the arguments to contain `event`. Adobe Launch has it owns `event` object which can be leveraged in your code. It provides details about what type of rule is being used to trigger this action but it also provides `event.detail` which contains the metadata provided as payload when a direct call rule is triggered.

```javascript
_satellite.track('myDCR', { test: 123, prop1: ['a', 'b']})
```

Without adding the `event` argument we will lose this payload. Even if you are not planning to use the `event` object, it is still a best practice to reference it. If you omit it, any data element which rely on the `event` object will not return the correct data. So you reference it for other features to use it.

> You can call a data element in the code using `_satellite.getVar('myDataElement', event)` and in the data element view you can leverage the `event` object using `%event.detail.test%`

Now your code should look as follow:
```javascript
'use strict';

module.exports = function(settings, event) {
  // TODO Perform some action.
};
```

As mentioned, the settings saved in the extension configuration view are not saved in a `settings` object passed as argument to a function. You will need to use `turbine.getExtensionSettings()` in your extension code to leverage the extension settings. This can be used in any JavaScript file of your extension.

## Conclusion

I have now described how to create a view for your extension and use the `settings` configured to use in your code. Developing your own private extension will allow you to save a lot of time while tagging. 

It is also in line with the DRY principle which is part of our [tagging]( https://dev.to/alcazes/adobe-launch-tagging-standards-3aak) and [coding]( https://dev.to/alcazes/adobe-launch-coding-standards-e22) standards