20
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
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.
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;
}
});
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.
- Create the
css
folder in theview
folder - Create the
js
folder in theview
folder - Create the
helpers
folder in thejs
folder - Go to Bulma website and download the latest build. Place the bulma.min.css in the css folder
-
Add the file
viewHelpers.js
inside thejs/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
-
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>
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
andopenCodeEditor
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 propertycustomCode
. 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.
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 theactions
folder) - it creates a JavaScript file in the corresponding folder type in the
lib
folder (i.e: an action will be saved in theactions
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 themain
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 theevent
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.
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.
20