Build a view for your Adobe Launch extension

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:

<!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.

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.

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.

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

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 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

        //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

    <!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/[email protected]/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.

<!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/[email protected]/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:

"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.

'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.

_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:

'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 and coding standards

20