# Dynamic content

URL: https://docs.ablyft.com/developers/application-integration/dynamic-content/

> Run experiments on content that appears after the page has loaded, such as modals, tabs and AJAX content.



Not everything a visitor sees is there when the page loads. Modals, cart drawers, tabs, checkout steps and content from
an API call appear later, often without changing the URL. This page shows how to make ABlyft wait for them.

There are two separate questions:

1. **When should the experiment become active?** Solved with the *trigger* of a [page](https://docs.ablyft.com/guides/audiences-and-delivery/pages/).
2. **When can my variation code change an element?** Solved with helper functions in your code.

## Make the page wait: choose a trigger

Each page has a **Trigger** that defines when ABlyft checks the page's targeting. Open **Targeting & Goals → Pages**,
edit the page, and go to the section **Trigger**.

| Trigger                                                 | Use it when                                                                                |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| **Direct (immediately when ABlyft runs)**               | The content is part of the page at load (default for most projects)                        |
| **DOM Change (when something on the page is changing)** | The page should be active as soon as certain elements exist                                |
| **Callback (when the callback function is called)**     | You decide in a small piece of JavaScript when the page starts                             |
| **API (manual activation by an API / JavaScript call)** | Your own application tells ABlyft when the page starts                                     |
| **URL Change …**                                        | The URL changes, see [SPA navigation](https://docs.ablyft.com/developers/application-integration/spa-navigation/) |

### DOM Change trigger

With **DOM Change**, ABlyft checks the page once at start and then again every time the page changes (elements are added,
removed or their attributes change). Changes caused by ABlyft's own code are ignored.

To use it, combine the trigger with **JavaScript rules** that describe the dynamic content. Open **Advanced
Targeting** in the section **Targeting** of the page and write a condition that returns `true` when the content exists:

```js
// Page is active as soon as the cart drawer is open
return document.querySelector('.cart-drawer.is-open') !== null;
```

The condition is valid if your JavaScript returns `true`. Inside it you can use `ablyftTools`.

Two options in **Advanced Targeting** help with late content:

| Option                      | Effect                                                                                                                                         |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **Poll on rules**           | ABlyft checks the condition again and again for up to 2 seconds before it gives up. Use it when the element appears shortly after the trigger. |
| **Always embed in snippet** | Usually not needed. Only enable it if your setup requires it.                                                                                  |

> **DOM Change runs often:** A busy page changes its DOM many times per second, and ABlyft checks the page every time. Keep JavaScript rules
> short and fast, and avoid heavy work in them.

### Callback trigger

With **Callback**, you write the logic. A field for JavaScript appears below the trigger. ABlyft runs your code
when it starts (and again on every re-run, for example after `ablyft.reInit()`). Call `activate()` when the page should be active. You can also use `ablyftTools` and `page`
(an object with the page `id`).

```js
// Activate the page when the checkout step 2 is rendered
ablyftTools.waitForElement('.checkout-step-2', () => {
  activate();
});
```

Call `activate(false)` to deactivate the page again, for example when a modal is closed:

```js
document.addEventListener('modal-opened', () => activate());
document.addEventListener('modal-closed', () => activate(false));
```

`activate()` still checks the page's URL rules and JavaScript rules. It does not activate the page if they do not match.

### API trigger

With **API**, your own code activates the page with an `activatePage` call, for example in the handler that opens your modal,
and deactivates it with `deactivatePage`. Setup and calls are described in the [Page API](https://docs.ablyft.com/developers/reference/page-api/).

### What happens when the page deactivates

The **deactivation mode** of the page (**Advanced Settings → Deactivation mode**) controls whether Reset JS runs and
whether the page stays active. See [SPA navigation](https://docs.ablyft.com/developers/application-integration/spa-navigation/#deactivation-modes).

## Make the code wait: helper functions

Even on an active page, an element may not exist yet. In variation code, use the
[tools](https://docs.ablyft.com/developers/reference/javascript-api/tools/), available as `ablyftTools`. The variation editor shows a **Code Helper**
with ready-made snippets for the most common ones.

| Function                                                                                                                  | Use it to                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [`waitForElement`](https://docs.ablyft.com/developers/reference/javascript-api/tools/#waitforelement)                                            | Run code for every element that matches a selector as soon as it exists, also elements added later |
| [`elementIsInView`](https://docs.ablyft.com/developers/reference/javascript-api/tools/#elementisinview)                                          | Run code when an element scrolls into the viewport                                                 |
| [`domChanged`](https://docs.ablyft.com/developers/reference/javascript-api/tools/#domchanged)                                                    | Run code on every DOM change                                                                       |
| [`domLoaded`](https://docs.ablyft.com/developers/reference/javascript-api/tools/#domloaded)                                                      | Run code when the DOM is ready                                                                     |
| [`poll`](https://docs.ablyft.com/developers/reference/javascript-api/tools/#poll) / [`check`](https://docs.ablyft.com/developers/reference/javascript-api/tools/#check) | Wait for any condition you define                                                                  |

```js
// Variation JS: change a button that is rendered later
ablyftTools.waitForElement('.add-to-cart', (button) => {
  button.textContent = 'Add to basket';
});
```

`waitForElement` calls your function once per element. If the element is replaced by your application (for example, re-rendered),
the new element is handled too.

## Dynamic data: re-check audiences

Audiences are evaluated when an experiment starts. If a value becomes known later, for example a cookie is set or a
data layer entry is pushed, tell ABlyft to look again:

```js
ablyft.push({ eventType: 'reevaluateAudiences' });
```

ABlyft then re-evaluates the audiences of experiments on currently active pages that are not running yet. Visitors
who were already excluded from an experiment stay excluded.

For audiences that depend on something happening on the page, you do not need to call it yourself. Audience rules of
the type **JavaScript Trigger** (call `activate()` when the audience should match) and **dataLayer Event** trigger the
re-check automatically. See [Data layer events](https://docs.ablyft.com/developers/tracking-and-events/datalayer-events/).

## Troubleshooting

* **Page never activates:** check the JavaScript rules in the console of your browser and turn on
  [debug mode](https://docs.ablyft.com/developers/debugging/preview-and-live-log/#debug-mode). Activated pages are logged.
* **Page activates, but the change is not applied:** your selector does not match, or the element appears after your
  code ran. Wrap the change in `ablyftTools.waitForElement`.
* **Change disappears when the app re-renders:** use `waitForElement` instead of a one-time change, so every new copy of the element is changed.

## Next steps

- [SPA navigation](https://docs.ablyft.com/developers/application-integration/spa-navigation/): URL triggers and reInit().
- [Custom JavaScript](https://docs.ablyft.com/developers/application-integration/custom-javascript/): Where to write experiment code.
- [Tools](https://docs.ablyft.com/developers/reference/javascript-api/tools/): All helper functions.

