npm package: @swissfederalrailways/internet-tracking
Description: A build and test tool for JS tracking code at SBB
Latest version: 16.0.12

# sbb-internet-tracking

> Declarative and imperative library that implements the [SBB Tracking Guide](https://confluence.sbb.ch/display/TRACKING/SBB.ch+Tracking+Guide).

## Install<a name="Install"></a>

The Library is hosted under the internal SBB Artifactory. To install it from this registry you need to have the following registry configured in your npm config: https://www.npmjs.com/package/@swissfederalrailways/internet-tracking

We are currently moving the library to bintray.com. Updated version 

```
npm install @swissfederalrailways/internet-tracking
```

## Module Format
The module is in UMD format and can therefore be imported in multiple ways:

```javascript
var tracking = require('@swissfederalrailways/internet-tracking');
```
```html
<script src="../path/to/@swissfederalrailways/internet-tracking/dist/main.js"></script>
```
With the global script import, the library will then load itself into `window.sbbInternetTracking`

## Usage<a name="Usage"></a>

### Initialization
The Library can track clicks and impressions by inspecting HTML attributes automatically. We use `data-sit-*` attributes for this.
All clicks are tracked automatically. To initialize the click and impression tracking you have to call the following after your HTML Elements are ready:
```javascript
sbbInternetTracking.scanForElementsToTrack();
```
Furthermore, you can call this method whenever there are new elements coming to the DOM that should now be tracked.

The first event that should be tracked is when the page is loaded.
```javascript
// fire on every pageload
sbbInternetTracking.dataLayerApi.pageLoaded({
  pageInstanceID: '571882',
  destinationURI: '/freizeit-ferien/reisen-schweiz/regionen/vier-jahreszeiten-im-wallis',
  pageName: 'Vier Jahreszeiten im Wallis',
  language: 'de',
  primaryCategory: 'sbb.ch',
  // all the following values are optional
  sysEnv: 'production',
  pageID: '1234',
  // If the page is an error page
  errorCode: '404',
  errorMessage: 'This page does not exist.',
  user: { 
    loginType: 'SwissPass B2C',
    loginStatus: 1,
    language: 'fr', // This is the user language, not the current language of the website
  }
})
```

To signal that the page is ready for the user and the tracking of the data layer should start, the following method can be called as soon as the page load data is ready:
```javascript
sbbInternetTracking.dataLayerApi.dataLayerReady();
```
If the page changes without a reload the two events "Page Load" and then "Data Layer Ready" have to be dispatched again.

### General Click Tracking
Generally all clicks on or inside Elements with an `href` attribute are tracked. To give more details for specific links the following attributes can be used:
If you have a navigation click that should be tracked, but is not on an Element with an `href` attribute you can use the `data-sit-href` attribute.
When using this attribute you should only pass absolute URLs since this won't get automatically resolved by the browser.
```html
<a href="...">I am tracked</a>

<div data-sit-href="...">
    <span>I am tracked as well</span>
</div>
```

When tracking clicks, the clicked element and the parent elements are sent to the analytics, which allows more detailed 
analysis of what specific items the user clicks. This is done via the dom-component-path library: https://code-ext.sbb.ch/projects/WCMS/repos/sbb-internet-tracking-dom-component-path.
The identifieres of these elements are derived from the most specific attribute we can find which is either:
- `data-sit-component` Attribute
- ID
- Class
- HTML Tag

#### Enhanced Click Tracking
If some relevant HTML Node in the tree is not specific enough, we can add a `data-sit-component="component name"` to it.
This allows for more details in later analysis and also position information when there are siblings of the same element.

If you need additional information to this click you can add an `data-sit-primary-category` or `data-sit-action` attribute to your link.
```html
<a 
    href="..." 
    data-sit-action="my action"
    data-sit-primary-category="my category"
>I am tracked with additional information</a>
```

If you want to pass in additional information which should be stored on the top level of an event (not inside the composedPath), you can
utilize `data-sit-location`. If the element that is clicked does not have this attribute set, the library searches the DOM hierarchy until it finds a value - if any.
```html
<div data-sit-location="footer">
    Every click inside me has the location attribute
</div>
```

In case you need another property that behaves like data-sit-location, there is `data-sit-variant`, which behaves in exactly the same way.
```html
<div data-sit-variant="departure">
    Every click inside me has the variant attribute
</div>
```

Similarly, there is `data-sit-detail`, which behaves in exactly the same way.
```html
<div data-sit-detail="detail">
    Every click inside me has the detail attribute
</div>
```

### Search
#### Search Page Initialization
Tracking the appearance of the search result page can be done two ways, depending on when you have the data available.
If the data is ready upon pageload you can pass the according values on the `pageLoaded` call:
```javascript
sbbInternetTracking.dataLayerApi.pageLoaded({
  // default pageLoaded parameters (see above)
  // ...
  // search parameters
  search: {
    level: 'primary',   // 'primary' |'secondary'
    value: 'search term',
    results: 42,
  }
});
```
When you don't have the data ready on pageload you can pass it later on using the `searchLoaded` function. Be aware that this function has to be called before calling the "dataLayerReady" method.
```javascript
sbbInternetTracking.dataLayerApi.searchLoaded({
  value: 'search term',
  results: 42,
  level: 'primary',
});
```

It is also possible to add this payload in the markup of your page, the "dataLayerReady" method will then pick it up and push it to the data layer before dispatching the event.
```html
<div
    data-sit-search-loaded="{\"value\": \"search term\", \"results\": 42, \"level\": \"primary\"}"
>...</div>
```

#### Search Result Click
Search results on the result page need to be annotated like this:
```html
<!-- Search Result (only on search result page - for search suggestion dropdown see below) -->
<a
    href="..."
    data-sit-search-result
    data-sit-search-value="Search query"
    data-sit-search-result-level="primary"          <!-- "primary" | "secondary" -->
    data-sit-search-result-label="Result label"
    data-sit-search-result-position="5"             <!-- indexing starts with 0 -->
    data-sit-search-result-page="2"                 <!-- indexing starts with 0 -->
>...</a>
```

#### Search Dropdown Click
Clicks on an item suggested by the dropdown of the search box are tracked manually by using the following code:
```javascript
sbbInternetTracking.dataLayerApi.trackSearchResultNavigation({
    destination: 'absolute/destination/of/link',
    component: clickedElement,
    label: 'Label of clicked element',
    value: 'Search term',
    type: 'autosuggest',
    level: 'primary',    // 'primary' | 'secondary'
    position: 2, // 0 indexed
    callback: () => window.location.href = '/wherever',
})
```
The type indicates hereby if the click occured on a suggested search or on an item in the _most searched_ list.
The position describes the position of the item in the list. The two lists of _autosuggest_ and _most searched_ have their own indexes.

The _callback_ parameter will be called as soon as the tracking request has been sent or at least after 300ms. Before redirecting you should therefore wait for the callback to be called.

In cases where a dropdown click does not lead to a new page being loaded, a custom event could be used instead. For example:
```javascript
sbbInternetTracking.dataLayerApi.sendCustomEvent({
  eventName: 'search result display',
  primaryCategory: 'primary search',
  value: "sbb",
  resultPageNumber: 1,
  results: 621
})
```

### Teaser
#### Declarative Setup
Standard teasers can be declaratively tracked using the following attributes:
```html
<a
    href="..."
    data-sit-teaser
    data-sit-teaser-headline="Teaser Headline"
    data-sit-teaser-label="Teaser Label"            <!-- optional -->
>...</a>
```

#### Manual Triggered Setup
Special Teasers like Carousels should trigger manually when a Teaser becomes visible since our library can't reliably determine this.

To ensure that it is not tracked twice, the `data-sit-teaser` has to be set to manual:
```html
<a
    href="..."
    data-sit-teaser="manual"
    data-sit-teaser-headline="Teaser Headline"
    data-sit-teaser-label="Teaser Label"            <!-- optional -->
>...</a>
```
After this, the `dataLayerApi` allows you to tell the library when a teaser is visible for the customer and when it has gotten invisble:
```javascript
// When becoming visible:
sbbInternetTracking.observeTeaserElement(element);

// When becoming invisible:
sbbInternetTracking.unobserveTeaserElement(element);
```
The library will ensure that each impression is fired only once when certain thresholds are met.
When the element is removed from the DOM it will not be tracked anymore.

### Impression
Impressions are a more general form of teasers and can therefore be used for more use cases.
Impressions fire events as soon as they enter the viewport fully.

```html
<div
    data-sit-impression
    data-sit-impression-type="Type"
    data-sit-impression-label="Impression"
    data-sit-impression-headline="Headline"
>
    ...
</div>
```

Similar to teasers, if the impression element is not present at the time of the page load, the library has to be informed when it can start looking for such elements:

```javascript
// When becoming visible
sbbInternetTracking.observeImpressionElement(element)

// When becoming invisible
sbbInternetTracking.unobserveImpressionElement(element)
```

Further, it is also possible to manually add an impression event to the datalayer:

```javascript
sbbInternetTracking.trackImpression({
  type: 'type',
  headline: 'headline',
  component: { ... },
  label: 'label'
})
```

### Accordions
Accordions should be tagged as such by setting the attributes `data-sit-component` on the accordion elements. 
It's important that equivalent items are siblings. That way we can calculate their relation to each other.
```html
<div>
    <div data-sit-component="accordion item"><span>My item 1</span></div>
    <div data-sit-component="accordion item"><span>My item 2</span></div>
    <div data-sit-component="accordion item"><span>My item 3</span></div>
</div>
```

To track accordion actions simply call `trackAccordionAction` on the dataLayerApi whenever an accordion item openes or closes.
```javascript
sbbInternetTracking.dataLayerApi.trackAccordionAction({
  action: 'open',
  component: accordionElement,
  headline: 'Accordion Title',
  label: 'Element label',
})
```

Since accordion actions (open/close) are likely to be called on regular clicks on the accordion item, the tracking library will also track this generic "click" event, not just the one you tiggered manually in the code above. 
To prevent this we have to exclude the element that opens/closes the accordion from the global click tracking like so:
```html
<div data-sit-component="accordion item">
    <button data-sit-ignore-clicks>Open Accordion</button>
    <div>My Content</div>
</div>
```

### Downloads
Download links or buttons need to be set up like the following:
```html
<!-- Download Link -->
<a
    href="..."
    data-sit-download
    data-sit-download-label="Download Label"
    data-sit-download-filename="Filename.extension"
>...</a>

<!-- Download Button -->
<button
    data-sit-href="..."
    data-sit-download
    data-sit-download-label="Download Label"
    data-sit-download-filename="Filename.extension"
>...</button>
```

### Update Login Type
The Login Type of the current user can be changed at runtime by calling the `updateUserLoginType` method on our dataLayerApi.
```javascript
sbbInternetTracking.dataLayerApi.updateUserLoginType({ loginType: 'SwissPass B2C' });
```

### Fine Tuning
#### Label Text
In the click tracking we search the DOM tree for a useful label. We already exclude elements with the following css attributes:
```css
.ignored-attributes {
    display: none;
    visibility: hidden;
    opacity: 0;
}
```
If you need to exclude other elements you can provide the class names by calling the function 
```javascript 
sbbInternetTracking.ignoreTextOfElementsWith('classNames', 'to', 'ignore')
```
Or there is also the declarative way with the attribute `data-sit-ignore-label`:
```html 
<span data-sit-ignore-label>
    Ignored Text
</span>
```

If you need to overwrite contents like sensible user data you can use the `data-sit-replace-label-with` attribute.
```html
<span data-sit-replace-label-with="USER_DATA">
    Sensible Data
</span>
```

#### Ignore clicks
The global click tracker will pick up any clicks on the page. Sometimes we want to prevent this behaviour for certain elements.
To exclude elements from being tracked you can add the following property:
```html
<div data-sit-ignore-clicks></div>
```

### Custom Events
There might be situations where you would like to manually push events onto the datalayer. For this, we have the following method
available on the dataLayerApi.
```typescript
sbbInternetTracking.dataLayerApi.sendCustomEvent({
    eventName: 'eventName', 
    primaryCategory: 'primaryCategory', 
    additional: 'additional', 
    additionalNested: {
        nested: 'nested'
    }
})
```

## Contribute

### Running Tests


``` bash
# install dependencies
npm install

# run tests on save
npm run tdd
```

#### Test Driven Development

The Test Driven Development approach is split into 2 phases:

1. Write (failing) tests for the new functionality.
2. Adapt the code to fix the tests.

This Project is structured for this type of development.

You find all the tests in the `/tests` folder.

When you run the following command, your file-system is watched, and every time
you save a file, the tests are atomatically re-run:

```bash
npm run tdd
```

If you wish to dive deeper into the actual setup of the tests, start in the
following file:

```
/karma.conf
```

#### BrowserStack

BrowserStack is a service that gives us the possibility to run the tests on
various different devices.

You need to set the following environment variables in order to connect to
the BrowserStack API:

```
BROWSERSTACK_USERNAME
BROWSERSTACK_KEY
```

The easiest way is to define these variables in the `rc` file of you shell, e.g.
`~/.zshrc` or `~/.bashrc`.

The `BROWSER` environment variable defines what browser the tests are run on.
You can prefix the `npm run` command with the environment variable as follows:

```bash
# run the test on all configured browsers
BROWSER=all npm run test

# run the tests on IE11 on BrowserStack
BROWSER=ie npm run test 

# run the tests on your local Chrome Browser
npm run test
```

The browsers are defined under the `customLaunchers` key in the `karma.conf.js`
file.

### Code Conventions

This project follows the [JavaScript Standard Style](https://standardjs.com/rules.html#javascript-standard-style), which is enforced through
ESLint. The `npm run build` task will fail if there are style errors in the
code, hence it is highly recommended to install and run an ESLint plugin in
your editor of choice.