Exp is a framework for building Exponea web-layers. It is designed to be used by (but not limited to) Exponea web-layer editor. Exp reduces the time to develop new web-layers, minimizes the amount of bugs and errors, simplifies tracking of different input fields, simplifies recommendation and list rendering and adds number of custom triggers.
The official guide assumes intermediate level knowledge of HTML, CSS, and JavaScript. If you are totally new to frontend development, it might not be the best idea to jump right into a framework as your first step - grasp the basics then come back! Prior experience with other frameworks helps, but is not required.
The easiest way to try out Exp is by duplicating one of the default web-layer templates or you can check out this JSFiddle Hello World example. Or you can test it locally by creating index.html
and include Exp with:
<script src="https://exp-framework.exponea.com/exp.js"></script>
Disclaimer: please do not use the script tag above for production purposes.
At the moment Exp framework can be added to clients website in several ways:
As the adoption of the Exp framework rises we will add it to our JavaScript SDK and installation will not be necessary.
Exp simplifies the development in the web-layer editor. Install the framework, preferably by pasting the source code at the beginning of the web-layer and add following code. Don't worry we will explain what it does in later sections. Also this section assumes that you are developing the web-layer in Exponea web-layer editor.
var banner = new Exp({
context: this
})
return {
remove: banner.removeBanner
}
This is the minimal amount of code required to display a web-layer, given valid HTML and CSS.
The code above consists of 3 main parts:
1. var banner = new Exp({})
This is the main definition of the Exp framework. It accepts only one argument, settings
object. In later sections we will cover all the settings that can be defined in the object.
2. context: this
context
is the context of the web-layer. The way Exponea web-layers are implemented is by storing all the information in what we call context object. This object stores info such as the HTML, CSS, JavaScript and also other settings such as SHOW ON from web-layer settings. The context object is referenced by this
.
3. return { remove: banner.removeBanner }
Exponea wep-layer editor implementation requires JavaScript part to return an object that has remove
method that removes the web-layer. In shor this makes sure that when you change HTML, CSS, JS the editor can easily refresh what you see.
At the core of Exp is a system that enables us to declaratively render text to DOM using exp-bind
attribute.
<div>
<p exp-bind="message"><p>
</div>
JavaScript:
var banner = new Exp({
context: this,
data: {
message: "Hello world!"
},
branded: false
})
Output:
Congratulations, you have made your first Exp app! exp-bind
makes sure that the data and DOM are linked, and everything is now reactive. How do we know? Open your browser's JavaScript console (right now, on this page) and set banner.message
to a different value. You should see the rendered example above update accordingly.
The branded: false
settings disables Powered by Exponea branding that is added by default. For now let's keep it off.
Note that exp-bind
changes only text. You can use exp-html
to set HTML of the element.
In addition to text binding, we can also bind element attributes like this:
HTML:<div>
<span exp-title="messge">
Hover your mouse over me for a few seconds
to see my dynamically bound title!
</span>
</div>
JavaScript:
var banner2 = new Exp({
context: this,
data: {
message: "You loaded this page on " + new Date().toLocaleString()
},
branded: false
})
Output:
Attributes starting with exp-
are called directives. Directives prefixed with exp-
indicate special attribute provided by Exp, and as you may have guessed they apply the special reactive behaviour to the rendered DOM. Here, it is basically saying "keep this element's title
attribute up-to-date with the message
property on the Exp instance."
If you open up your JavaScript console again and enter banner2.message = 'some new message'
you'll once again see that the bound HTML - in this case title
attribute - has changed.
It's easy to toggle the presence of an element, too:
HTML:<div>
<span exp-if="seen">
Now you see me
</span>
</div>
JavaScript:
var banner3 = new Exp({
context: this,
data: {
seen: true
},
branded: false
})
Output:
Go ahead and enter banner3.seen = false
in the console. You should see the message disappear.
There are quite a few directives, each with its own special functionality. For example, the exp-for
directive can be used for displaying a list of items using the data from an Array:
<div>
<ul>
<li exp-for="product in products" exp-bind="product.name"></li>
</ul>
</div>
JavaScript:
var banner4 = new Exp({
context: this,
data: {
products: [
{ name: "Shoes" },
{ name: "Belt" },
{ name: "T-shirt" },
]
},
branded: false
})
Output:
In the console, enter banner4.products.push({ name: 'Coat' })
. You should see a new item appended to the list.
Exp does not support nested loops at the moment.
To let users interact with your banner, we can use the exp-{event}
directive to attach event listeners that invoke methods on out Exp instance:
<div>
<p exp-bind="message"></p>
<button exp-click="reverseMessage">Reverse Message</button>
</div>
JavaScript:
var banner5 = new Exp({
context: this,
data: {
message: 'Hello Exp!'
},
methods: {
reverseMessage: function() {
this.message = this.message.split('').reverse().join('')
}
},
branded: false
})
Output:
Note that in this method we update the state of our banner without touching the DOM - all DOM manipulations are handled by Exp, and the code you write is focused on the underlying logic.
At the moment following events are supported: "click", "submit", "input", "hover", "blur", "focus", "mouseenter", "mouseleave"
Exp also provides the exp-model
directive that makes two-way binding between form input and banner state a breeze:
<div>
<p exp-bind="message"></p>
<input type="text" exp-model="message">
</div>
JavaScript:
var banner6 = new Exp({
context: this,
data: {
message: 'Hello Exp!'
},
branded: false
})
Output:
Every Exp banner starts by creating a new Exp instance with Exp
function:
var banner = new Exp({
// settings
})
When you create an Exp instance, you pass in an settings object. The majority of this guide describes how you can use these options to create your desired behavior. For reference, you can also browse the full list of options in the API reference.
When an Exp instance is created, it adds all the properties found in its data object to Exp’s reactivity system. When the values of those properties change, the view will “react”, updating to match the new values.
// Our data object
var data = { a: 1 }
// The object is added to a Vue instance
var banner = new Exp({
data: data
})
// Getting the property on the instance
// returns the one from the original data
banner.a == data.a // => true
// Setting the property on the instance
// also affects the original data
banner.a = 2
data.a // => 2
// ... and vice-versa
data.a = 3
banner.a // => 3
When this data changes, the view will re-render. It should be noted that properties in data
are only reactive if they existed when the instance was created. That means if you add a new property, like:
banner.b = 'hi'
Then changes to b
will not trigger any view updates. If you know you’ll need a property later, but it starts out empty or non-existent, you’ll need to set some initial value. For example:
data: {
message: 'Hello world',
products: [],
error: null
}
The only exception to this being the use of exp-rcm
, which we will discuss later in the guide.
In addition to data properties, Exp instances expose a number of useful instance properties and methods. These are prefixed with $
to differentiate them from user-defined properties. For example:
var banner = new Exp({
context: this,
data: {
email: 'adam@example.com'
}
})
banner.$validateEmail(banner.email) // => true
In the future, you can consult the API reference for a full list of instance properties and methods.
This section is work in progress.
Exp allows you to define formatters that can be used to apply common text formatting. Formatters are usable only with exp-bind
directive. Formatters should be appended to the end of the expression denoted by the "pipe" symbol:
<p exp-bind="message | uppercase"></p>
You can define formatters on the Exp instance.
var banner = new Exp({
context: this,
data: {
message: 'hi there'
},
formatters: {
uppercase: function(value) {
return value.toUpperCase()
}
},
branded: false
})
Below is an example of our uppercase
formatter being used:
The formatter’s function always receives the expression’s value (the result of the former chain) as its first argument. In the above example, the uppercase
formatter function will receive the value of message as its argument.
Formatters can be chained:
<p exp-bind="message | formatterA | formatterB"></p>
Exp includes a small utility for working with Exponea recommendations. exp-rcm
directive, works the same way as exp-for
but it does a little more work in the background to make our lifes easier.
Below is an example of using exp-rcm
for rendering 4 products with their image, name, price and link in a table:
<div>
<table>
<tr>
<th>Name</th>
<th>Price</th>
<th>Link</th>
<th>Image</th>
</tr>
<tr exp-rcm="product in products">
<td exp-bind="product.name"></td>
<td exp-bind="product.price"></td>
<td><a exp-href="product.link" target="_blank">link</a></td>
<td><img exp-src="product.image" width="100"></td>
</tr>
</table>
</div>
JavaScript:
var banner = new Exp({
context: this,
recommendations: {
products: {
id: "<recommendation id>",
total: 4
}
}
})
Output:
Name | Price | Link | Image |
---|---|---|---|
link |
You can define recommendations
object on the Exp instance. The object holds the definitions of recommendations. Each key (in example above products
) reserves an Array in the instance data. Each recommendation definition must have id
and total
. Exp will automatically fetch the products and fill in the reserved Array.
Recommendation fetching is asynchronous and it is possible that the web-layer will be displayed before the response arrives and before the product list is rendered. To mitigate this we have added two optional settings. loadingKey
and deferTrigger
.
1. loadingKey
changes the value of variable defined on isntance to true
when the response from recommendations API arrives.
var banner = new Exp({
context: this,
recommendations: {
products: {
id: "<recommendation id>",
total: 4,
loadingKey: 'doneLoading'
}
},
data: {
doneLoading: false
}
})
2. deferTrigger
will simply disable banner triggers until the recommendations API returns response.
var banner = new Exp({
context: this,
recommendations: {
products: {
id: "<recommendation id>",
total: 4,
deferTrigger: true
}
},
data: {
doneLoading: false
}
})
When the banner is displayed to the user it will automatically track banner
event with action: show
and same properties as our old banners.
It is possible to disable automatic tracking of the banner by setting tracking: false
on the Exp instance.
var banner = new Exp({
tracking: false
})
Exp adds two directives for tracking:
1. exp-action
directive will track action: show
event on click.
<button exp-action>Click me</button>
2. exp-close
directive tracks action: close
event and removes the banner.
<span exp-close>X</span>
Exp is fully integrated with Sentry.io. Sentry is an open-source error tracking tool that helps developers monitor and fix crashes in real-time.
By default Sentry is turned off. Sentry can be enabled on Exp instance:
var banner = new Exp({
context: this,
sentry: {
use: true, // default false
noConflict: true, // default true
project: '', // sentry project URL
options: {} // sentry initialization options (for example tags)
}
})
By default Exp will make embed Raven JavaScript SDK from Sentry CDN. It will run Raven in noConflict
mode which will create new instance of Raven object per each web-layer. noConflict
mode can be set to false to use clients Raven integration. One downside of disabling noConflict mode is that we will lose the ability to correctly identify which web-layers are causing errors. It is recommended to always use noConflict: true
.
If possible Exp will try to tag errors with web-layer ID, web-layer name and project-token.
By default all of the new banner templates using Exp are GDPR compliant. Beware that using Exp for web-layer doesn't imply GDPR compliance. All the banner templates collecting customer data track consent
event.
Example implementation:
this.sdk.track('consent', {
action: 'accept',
category: '[[ consentCategory ]]',
valid_until: 'unlimited'
});
If you wish to develop custom web-layer and you need to track consent
events please consult technical consultants.
In this section we will show you example code on how to implement basic web-layers such as newsletter collection, countdown offer or on-exit full screen web-layer.
<div class="exponea-banner">
<span class="close" exp-close>X</span>
<h2>Subscribe!</h2>
<input type="text" placeholder="@" exp-model="email"/>
<button exp-click="submit">Subscribe</button>
</div>
CSS:
.exponea-banner {
position: fixed;
width: 350px;
padding: 15px 15px 25px;
bottom: 20px;
right: 20px;
background: #ffd500;
}
.close {
position: absolute;
top: 15px;
right: 15px;
}
h2 {
margin: 0 0 10px;
}
input {
box-sizing: border-box;
width: 100%;
padding: 10px 15px;
margin-bottom: 10px;
border: 0;
}
button {
width: 100%;
padding: 10px 15px;
border: 0;
background: #1c1733;
color: #ffffff;
}
JavaScript:
var banner = new Exp({
context: this,
data: {
email: ''
},
methods: {
submit: function() {
if (this.$validateEmail(this.email)) {
this.sdk.update({ email: this.email });
this.removeBanner();
} else {
alert('Invalid email!')
}
}
},
scoped: true
})
Let's go over the HTML and JavaScript part and understand what exactly is happening.
First notice the exp-close
directive. It is responsible for adding a close functionality to the span element. By default anything that has exp-close
directive will track banner close event.
Now look at the input. It has exp-model="email"
directive. What it does is that it makes the email
variable in our data model reactive, so any time we change the text in the input the variable changes aswell.
Last important thing in HTML is the exp-click="submit"
directive on the button element. This will invoke the submit
method defined on our Exp instance.
JavaScript is even more simpler. The submit
method performs basic validation with built in method $validateEmail()
(all built in methods start with the dollar sign besides removeBanner()
). Value of the input is accessed by this.email
.
scoped: true
just makes sure that the CSS is applied only to the banner and nothing else on the site. Feel free to inspect the banner and see how it works :)
<div class="exponea-banner">
<span class="close" exp-close>X</span>
<h2>Limited time offer!</h2>
<span exp-bind="days"></span>days
<span exp-bind="hours"></span>hours
<span exp-bind="minutes"></span>minutes
<span exp-bind="seconds"></span>seconds left
</div>
CSS:
.exponea-banner {
position: fixed;
width: 350px;
padding: 15px 15px 25px;
top: 20px;
left: 20px;
background: #ffd500;
}
.close {
position: absolute;
top: 15px;
right: 15px;
}
h2 {
margin: 0 0 10px;
}
span {
font-weight: bold;
}
JavaScript:
var banner = new Exp({
data: {
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
end_time: new Date((new Date()).getTime() + (1000*60*2)),
show: true,
timer: null
},
methods: {
updateTime: function() {
var current_time = new Date();
var distance = this.end_time.getTime() - current_time.getTime();
if (distance < 0) {
this.removeBanner();
clearInterval(this.timer)
return;
}
var _second = 1000;
var _minute = _second * 60;
var _hour = _minute * 60;
var _day = _hour * 24;
this.days = Math.floor(distance / _day);
this.hours = Math.floor((distance % _day) / _hour);
this.minutes = Math.floor((distance % _hour) / _minute);
this.seconds = Math.floor((distance % _minute) / _second);
}
},
mounted: function() {
this.updateTime();
this.timer = setInterval(this.updateTime.bind(this), 1000)
}
})
HTML is rather simple here. Basic use of exp-bind
directives to display days, hours, minutes and seconds left.
JavaScript: Basic logic goes like this: create an interval that will update the days, hours, minutes and seconds left each second. We did this by calling setInterval(this.updateTime.bind(this), 1000)
in the mounted
lifecycle hook.
Note that it is important to use .bind(this)
to keep the context of the method same.