Documentation

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.


Getting Started

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.

Installation

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.

Development

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.

Data binding

At the core of Exp is a system that enables us to declaratively render text to DOM using exp-bind attribute.

HTML:
<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:
Hover your mouse over me for a few seconds to see my dynamically bound title!

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.

Conditionals and Loops

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:
Now you see me

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:

HTML:
<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.

Handling User Input

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:

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

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

The Exp instance

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.

Data and Methods

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.

Instance Lifecycle Hooks

This section is work in progress.

Formatters

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>

Recommendations

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:

HTML:
<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
  }
})

Event Tracking

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>

Sentry Integration

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.

GDPR Compliance (default banner templates)

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.

Common Use-cases

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.

Newsletter Collection

HTML:
<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 :)

Countdown Offer

HTML:
<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.

API Reference

Exp frameworks API is inspired by the amazing Vue.js framework. Big parts of this documentation is taken from Vue.js guide. All credits goes to its respective owners.