@douglas.onsite.experimentation/douglas-ab-testing-toolkit
Version:
DOUGLAS A/B Testing Toolkit
999 lines (750 loc) • 42.7 kB
Markdown
# DOUGLAS A/B Testing Toolkit <!-- omit in toc -->
**Öffentliche Doku** – keine internen URLs, Keys oder personenbezogenen Daten hier einfügen.
## Inhalt
- [Getting started](#getting-started)
- [Install](#install)
- [Usage Example](#usage-example)
- [Bundle size (production)](#bundle-size-production)
- [Testing](#testing)
- [Best practices](#best-practices)
- [API overview](#api-overview)
- [Helper Functions](#helper-functions) · [Wrapper](#wrapper-functions) · [Observer](#observer-functions) · [Data](#data-functions) · [Util](#util-functions)
- [Hotjar](#hotjar)
- [License](#license)
Detaillierte Signaturen und Parameter: JSDoc in [`douglas-toolkit.js`](./douglas-toolkit.js). Nicht jedes Export ist unten einzeln ausgeschrieben (siehe [API overview](#api-overview)).
# Getting started
This toolkit is designed to help you creating A/B tests. Have fun using it!
**Questions or feedback?**
<br/>
Max Vith <ma.vith@douglas.de>
<br/>
Kristina Bekher <k.bekher@douglas.de>
## Install
Scoped package name (siehe [`package.json`](./package.json)):
```sh
npm install @douglas.onsite.experimentation/douglas-ab-testing-toolkit
```
```sh
yarn add @douglas.onsite.experimentation/douglas-ab-testing-toolkit
```
```js
import { elem, qs } from '@douglas.onsite.experimentation/douglas-ab-testing-toolkit';
```
## Usage Example
```js
import { elem } from '@douglas.onsite.experimentation/douglas-ab-testing-toolkit';
elem('#example', (example) => {
// do something...
});
```
## Bundle size (production)
- **Tree-Shaking:** Immer **benannte Imports** nutzen (`import { qs, elem } from '…'`), kein `import * as toolkit`. Das Paket ist mit **`"sideEffects": false`** markiert, damit Bundler ungenutzte Exports entfernen können.
- **Minify:** Experiment-Build mit **esbuild / Terser / swc** minifizieren; optional **`drop_console`** oder nur Debug-Logs streichen, wenn ihr in Prod keine Logs braucht.
- **Kameleoon-Rohscript:** Wird das Toolkit **ohne Bundler** komplett eingebunden, gibt es **kein** Tree-Shaking – dann zählt die **gesamte** Datei. Dann: eigenes kleines Bundle nur mit den genutzten Imports bauen und **eine** minifizierte Datei in Kameleoon legen.
- **Aufteilen (optional):** Nur nötig, wenn ihr weiterhin das volle Monolit-Script ohne Bundler ausliefern müsst und trotzdem kleiner werden wollt – dann z. B. thematische Entry-Files + `package.json` **`exports`**.
- **`console.error`:** Wenn ihr bei `drop_console` **alle** `console.*` entfernt, betrifft das ggf. auch Fehlerausgaben aus `logError` – ggf. nur `log`/`info`/`debug` strippen.
## Testing
Im Paketroot (nur für Entwicklung des Toolkits, **nicht** im npm-Tarball enthalten):
```sh
npm install
npm run test # Watch
npm run test:run # CI / einmalig
```
Tests liegen unter `tests/`. Lokale Fixtures mit sensiblen Daten: `*.local.json` (siehe `tests/fixtures/README.md` und `.gitignore`).
## Best practices
- **Imports:** Benannte Imports, kein `import *` – nutzt Tree-Shaking (`sideEffects: false`).
- **Globals:** Viele Helfer nutzen `window` (z. B. `OE_TOOLKIT`, `OE_METRICS`, `System`, `Kameleoon`) – vor Aufruf prüfen, ob die Umgebung (Shop + Kameleoon) passt.
- **`share` / `exec`:** Global JS registriert mit `share`, Varianten-JS ruft `exec(name, params)`; ausstehende Aufrufe werden in einer Queue gehalten und nach `share` bzw. `exec()` ohne Name abgearbeitet.
- **Keine Secrets** in Experiment-Code oder öffentlicher Doku; Ziel-IDs und URLs möglichst generisch halten.
# API overview
Alle folgenden Symbole werden aus `douglas-toolkit.js` exportiert:
| Bereich | Exports |
|--------|---------|
| DOM / Warten | `qs`, `qsa`, `elem`, `elemSync`, `addClass`, `removeClass`, `hasClass`, `toggleClass`, `addPrefix`, `removePrefix`, `setMutationObserver`, `setClickEvent`, `setAccessabilityEvents` |
| Globals / Queue | `share`, `exec`, `asyncSave` |
| Kameleoon / Daten | `pushMetric`, `pushData` |
| Observer (Toolkit) | `addTask`, `removeTask`, `updateConfig` |
| Shop / URL | `getState`, `getProdId`, `getProduct`, `getProducts`, `getPage`, `goTo`, `pageChange`, `pushHistory` |
| Storage / Cookies | `getCookie`, `setCookie`, `getStore`, `setStore` |
| UI / Media | `getWidth`, `isMobile`, `scrollTo`, `inViewport`, `isElementInViewport`, `setPreload`, `loadLibrary`, `addStyleToBody` |
| Sonstiges | `hotjar` |
`logError` ist **nicht** exportiert (intern für `asyncSave` u. a.).
# Helper Functions
## elem
The `elem` function is using [document.querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) internally.
The `elem` function continuously checks for the presence of DOM elements matching a specified CSS selector or evaluates a custom condition provided as a function. It executes a callback when the condition is met or times out after 10 seconds.
### Parameters <!-- omit in toc -->
- selector (string | function): A CSS selector string to query elements from the DOM. Alternatively, a function that evaluates to true when the desired condition is met.
- callback (function): A function called when either:
- Matching elements are found, and the result is passed as an argument.
- The timeout of 10 seconds is reached without finding any elements or satisfying the condition, in which case false is passed to the callback.
### Behavior <!-- omit in toc -->
1. If selector is a string, it is converted into a function that queries elements using qsa(selector).
2. The function iteratively checks for the presence of elements or evaluates the condition every 33 milliseconds.
3. If elements are found or the condition returns a truthy value, the callback is invoked with the result.
4. If no elements are found within the 10-second window, the callback is called with false.
### Usage Example <!-- omit in toc -->
```js
elem('.my-class', elements => {
if (elements) {
console.log('Found elements:', elements);
} else {
console.log('No elements found within the timeout period.');
}
});
```
### Using a Custom Condition <!-- omit in toc -->
```js
elem(() => document.querySelector('#special-element') !== null, result => {
if (result) {
console.log('Special element is now available.');
} else {
console.log('Element not found within the timeout period.');
}
});
```
### Notes <!-- omit in toc -->
- The timeout period is hard-coded to 10 seconds.
- The polling interval is 33 milliseconds.
## elemSync
`elemSync` is an asynchronous function that waits for DOM elements to be available by utilizing a provided elem function. It returns a Promise that resolves with the elements matching the specified CSS selector.
### Parameters <!-- omit in toc -->
- selector (string): A CSS selector used to query the desired DOM elements.
### Return Value <!-- omit in toc -->
- Returns a Promise that resolves to:
elements: The elements found by the elem function, based on the provided selector.
### Usage Example <!-- omit in toc -->
```js
(async () => {
const elements = await elemSync(".my-class");
if(!elements) return;
console.log('Elements found:', elements);
})();
```
```js
elemSync('.my-class').then(elements => {
if (elements) {
console.log('Elements found:', elements);
} else {
console.log('No elements found.');
}
});
```
### Assumptions <!-- omit in toc -->
The function assumes that elem is a pre-existing function that accepts a CSS selector and a callback, providing matched elements asynchronously.
### Notes <!-- omit in toc -->
- Über `elem` wird bei Timeout `false` an den Callback übergeben; `elemSync` resolved entsprechend mit `false`.
## share
Registriert eine benannte Funktion auf **`window.OE_TOOLKIT`**, typischerweise im **Global Script** (Kameleoon). Danach werden **wartende** `exec`-Einträge für bereits vorhandene Handler aus der Queue ausgeführt.
### Parameters <!-- omit in toc -->
- **name** (string): Schlüssel auf `window.OE_TOOLKIT`.
- **func** (function): Aufzurufende Implementierung.
### Usage Example <!-- omit in toc -->
```js
share('myExperimentInit', (config) => {
// …
});
```
Rückgabewert: `window.OE_TOOLKIT`.
## exec
Ruft `window.OE_TOOLKIT[name](params)` auf, oder **queued** den Aufruf, bis `share` die Funktion registriert hat. **`exec()`** ohne Namen leert die Queue für alle Einträge, deren Handler bereits existieren.
### Parameters <!-- omit in toc -->
- **name** (string, optional): Handler-Name.
- **params** (any): Wird an den Handler übergeben.
### Notes <!-- omit in toc -->
- Mehrfaches `exec(name, …)` vor `share`: nur **ein** Queue-Eintrag pro `name`; **letzter** `params` gewinnt.
- Nach erfolgreichem direkten `exec` werden Queue-Einträge für diesen Namen entfernt.
### Usage Example <!-- omit in toc -->
```js
exec('myExperimentInit', { flag: true });
exec(); // Queue flushen, falls Handler schon registriert sind
```
## asyncSave
Wrap für **async** Funktionen: `try/catch`, Fehler werden intern per **`console.error`** (`>>> OE_ERROR`) geloggt. Standard: Fehler **nicht** erneut werfen.
### Parameters
- **fn** (`Function`): Async-Funktion.
- **prefix** (`string`, optional): Kontext für Logs.
- **options** (optional): `{ rethrow: true }` – nach Log den Fehler weiterwerfen.
### Usage Example
```js
const safe = asyncSave(async () => { /* … */ }, 'myExperiment', { rethrow: false });
await safe();
```
## pushMetric
You can use the `pushMetric` function for sending goals (unique and multiple).
The `pushMetric` function logs and optionally tracks unique conversion metrics during A/B testing experiments. It stores metric IDs in a global array (window.OE_METRICS) and provides placeholders for integrating with third-party analytics tools.
### Parameters <!-- omit in toc -->
- id (string): The identifier for the metric or goal to be tracked.
- unique (boolean) [optional]: If true, the function ensures the metric is only tracked once by checking if the id is already present in window.OE_METRICS. Defaults to false.
### Usage Example <!-- omit in toc -->
#### Track a Metric Once <!-- omit in toc -->
```js
pushMetric('goal_123', true);
```
Tracks the metric with ID goal_123 only if it hasn’t been tracked before.
#### Track the Same Metric Multiple Times <!-- omit in toc -->
```js
pushMetric('goal_123');
```
Allows repeated tracking of the same metric (mehrfache `processConversion`-Aufrufe möglich).
### Notes <!-- omit in toc -->
- Ruft **`Kameleoon.API.Goals.processConversion(id)`** auf (Kameleoon muss auf der Seite verfügbar sein).
- Fehler werden abgefangen und geloggt.
- **`window.OE_METRICS`** merkt sich bereits gesendete IDs (für `unique`).
## pushData
Setzt **Custom Data** über `Kameleoon?.API?.Data?.setCustomData(key, value, options)` (optional chaining – ohne Kameleoon keine Wirkung). Details siehe Kameleoon-Doku.
# Wrapper Functions
## qs
The `qs` function is just a wrapper for [document.querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
The `qs` function is a utility for selecting a single DOM element using a CSS selector. It simplifies element selection by optionally allowing searches within a specific parent element.
### Parameters <!-- omit in toc -->
- selector (string): A CSS selector string used to identify the desired element.
- parent (Element | Document) [Optional]: The element or document context within which the selector will be applied. Defaults to document if not provided.
### Return Value <!-- omit in toc -->
- Returns the first element within the specified parent that matches the given selector.
- If no matching element is found, it returns null.
### Usage Example <!-- omit in toc -->
```js
const button = qs('.submit-button');
if (button) {
button.addEventListener('click', () => console.log('Button clicked!'));
}
```
#### Selecting an element within a specific parent container <!-- omit in toc -->
```js
const container = document.getElementById('form-container');
const inputField = qs('input[name="username"]', container);
if (inputField) {
inputField.focus();
}
```
## qsa
The `qsa` function is just a wrapper for [document.querySelectorAll()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll).
The `qsa` function is a utility for selecting multiple DOM elements using a CSS selector. It allows querying within a specific parent element or the entire document.
### Parameters <!-- omit in toc -->
- selector (string): A CSS selector string used to identify the desired elements.
- parent (Element | Document) [Optional]: The element or document context within which the selector will be applied.
Defaults to document if not provided.
### Return Value <!-- omit in toc -->
- Returns a NodeList containing all elements that match the given selector within the specified parent context.
- If no elements match, an empty NodeList is returned.
### Usage Example <!-- omit in toc -->
```js
const buttons = qsa('.button-class');
buttons.forEach(button => {
button.addEventListener('click', () => console.log('Button clicked!'));
});
```
#### Selecting elements within a specific parent container <!-- omit in toc -->
```js
const container = document.getElementById('nav-container');
const links = qsa('a', container);
links.forEach(link => {
console.log('Link text:', link.textContent);
});
```
## addClass
The addClass function adds a specified CSS class to a given HTML element if the class is not already present. It ensures that the operation only proceeds when the element and class name are valid.
### Parameters <!-- omit in toc -->
- element (HTMLElement): The target HTML element to which the class should be added.
- selector (string): The CSS class name to add to the element.
### Return Value <!-- omit in toc -->
- Returns true if the class is successfully added.
- Returns undefined if the operation is not performed due to missing or invalid input.
### Usage Example <!-- omit in toc -->
```js
const button = qs('.my-button');
addClass(button, 'active');
```
Adds the class active to the element with the class my-button.
## removeClass
The removeClass function removes a specified CSS class from an HTML element if the class is present. It ensures that the operation only proceeds when valid input is provided.
### Parameters <!-- omit in toc -->
- element (HTMLElement): The target HTML element from which the class should be removed.
- selector (string): The CSS class name to be removed from the element.
### Return Value <!-- omit in toc -->
- Returns true if the class is successfully removed.
- Returns undefined if the operation is not performed due to invalid or missing input.
### Usage Example <!-- omit in toc -->
```js
const button = qs('.my-button');
removeClass(button, 'active');
```
## hasClass
The hasClass function checks whether a specified CSS class is present on a given HTML element. It ensures that the input parameters are valid before performing the check.
### Parameters <!-- omit in toc -->
- element (HTMLElement): The target HTML element to be checked for the specified class.
- selector (string): The CSS class name to check for.
### Return Value <!-- omit in toc -->
- Returns true if the element contains the specified class.
- Returns false if the class is not present or if the input is invalid.
### Usage Example <!-- omit in toc -->
```js
const button = qs('.my-button');
if (hasClass(button, 'active')) {
console.log('Button is active!');
}
```
## toggleClass
The toggleClass function toggles a specified CSS class on a given HTML element. It ensures that the input parameters are valid before performing the toggle.
### Parameters <!-- omit in toc -->
- element (HTMLElement): The target HTML element to be checked for the specified class.
- selector (string): The CSS class name to check for.
### Return Value <!-- omit in toc -->
- Returns true if the class is now present,
- Returns false if removed or invalid input.
### Usage Example <!-- omit in toc -->
```js
const button = qs('.my-button');
if (toggleClass(button, 'active')) {
console.log(hasClass(button, 'active'));
}
```
## addPrefix
The `addPrefix` function is a utility that applies a CSS class to the root <html> element of a document. It is typically used to add a global modifier or theme-related class for styling purposes.
### Parameters <!-- omit in toc -->
- selector (string):
The name of the CSS class to be added to the <html> element.
### Return Value <!-- omit in toc -->
- The function does not explicitly return a value but delegates to addClass, whose behavior depends on its implementation.
### Usage Example <!-- omit in toc -->
```js
addPrefix('dark-mode');
```
After executing this code, the <html> element will look like this if dark-mode is successfully applied:
```html
<html class="dark-mode">
```
### Notes <!-- omit in toc -->
The function directly modifies the root element (document.documentElement), affecting all styles tied to the specified class.
## removePrefix
The `removePrefix` function removes a specified CSS class from the root <html> element of a document. It is typically used to reset or revert global styles applied to the document.
### Parameters <!-- omit in toc -->
- selector (string): The name of the CSS class to be removed from the <html> element.
### Return Value <!-- omit in toc -->
- The function does not explicitly return a value but delegates to removeClass, whose behavior depends on its implementation.
### Usage Example <!-- omit in toc -->
```js
removePrefix('dark-mode');
```
After executing this code, if the class was previously applied, the <html> element would change as follows:
```html
<html class="">
```
### Notes <!-- omit in toc -->
The function directly modifies the root element (document.documentElement), affecting all styles tied to the specified class.
## setClickEvent
The `setClickEvent` function adds a click event listener to one or more elements specified by a CSS selector or direct reference. It tracks clicks by either invoking a callback function or pushing a metric event when the element is clicked. It prevents duplicate event bindings by marking elements using a data attribute.
### Parameters <!-- omit in toc -->
- selector (string | HTMLElement): A CSS selector string or a direct reference to an HTML element to which the click event listener will be added.
- prefix (string): A unique identifier used as a data attribute (data-[prefix]) to ensure that the click listener is not added multiple times to the same element.
- callback (function | string): If a function, it is executed when the element is clicked. If a string, it is assumed to be a metric identifier passed to pushMetric() for analytics tracking.
### Usage Example <!-- omit in toc -->
```js
setClickEvent('.btn-submit', 'trackClick', event => {
console.log('Button clicked!', event.target);
});
```
Adds a click listener to elements with the class .btn-submit and logs a message when clicked.
```js
setClickEvent('.btn-track', 'metricTrack', 'goal_123');
```
Tracks a metric with ID goal_123 when any element with the class .btn-track is clicked.
### Notes <!-- omit in toc -->
- Duplicate Prevention: Ensures event listeners are not added multiple times by using the data-[prefix] attribute.
- Callback Flexibility: Supports both custom callback functions and analytics tracking.
- Dependency: Relies on the elem() utility function for element selection. Ensure elem() is available for this function to work correctly.
# Observer Functions
## addTask
Adds a new task to the DOUGLAS Toolkit observer.
```js
addTask('UX1234', () => {});
```
## removeTask
Removes a task from the DOUGLAS Toolkit observer by name.
```js
removeTask('UX1234');
```
## updateConfig
Updates the observer configuration with a new config object.
```js
updateConfig({attributes: true});
```
# Data Functions
## getState
The `getState` function asynchronously retrieves the application state from a global store object located in window.System. The function returns a promise that resolves to the state if it is successfully retrieved or rejects in case of an error.
### Return Value <!-- omit in toc -->
- Type: Promise<any>
- Description: Resolves to the application state retrieved from window.System.store.getState(). If the store is not available, the promise rejects with an error.
### Example Usage <!-- omit in toc -->
```js
getState()
.then(state => {
console.log("Application state:", state);
})
.catch(error => {
console.error("Error retrieving state:", error);
});
```
## getProdId
The `getProdId` function extracts and returns a product ID from the current URL. It dynamically determines whether the product ID is found in the search query string or the pathname, depending on the presence of a “variant” parameter.
### Return Value <!-- omit in toc -->
- Type: string
- Description: Returns the extracted product ID from the URL.
### Example Usage <!-- omit in toc -->
```js
const productId = getProdId();
```
## getProduct
The `getProduct` function asynchronously retrieves product information by dispatching a request to the global System store using the product ID derived from the current page’s URL or from a provided ID. It returns a promise that resolves with the product data or rejects if the product is not found or if an error occurs during the data retrieval process.
### Parameters <!-- omit in toc -->
- productId (string, optional): The product ID to fetch. If omitted, the function will use `getProdId()` to extract the ID from the current page URL.
### Return Value <!-- omit in toc -->
- Type: Promise<any>
- Description: Resolves with the product data retrieved from the API. If the product is not found or an error occurs, the promise rejects.
### Example Usage <!-- omit in toc -->
```js
getProduct()
.then(product => {
console.log("Product retrieved:", product);
})
.catch(error => {
console.error("Error retrieving product:", error.message);
});
getProduct('12345')
.then(product => {
console.log("Product retrieved by ID:", product);
});
```
## getProducts
The `getProducts` function retrieves product data from the store's cached search results. If the `variations` parameter is set to `true`, it will fetch detailed product information for each product in the list by calling `getProduct` for each product code, and return an array of detailed product objects. Otherwise, it returns the cached product list from the store.
### Parameters <!-- omit in toc -->
- variations (boolean, optional): If true, fetches detailed product data for each product in the list. Defaults to false.
### Return Value <!-- omit in toc -->
- Type: Promise<any[]>
- Description: Resolves with an array of product objects. If `variations` is true, each object is a detailed product; otherwise, each object is from the cached search result.
### Example Usage <!-- omit in toc -->
```js
// Get cached product list from the store
getProducts()
.then(products => {
console.log("Cached products:", products);
})
.catch(error => {
console.error("Error retrieving products:", error.message);
});
// Get detailed product data for each product in the list
getProducts(true)
.then(productsWithDetails => {
console.log("Detailed products:", productsWithDetails);
});
```
# Util Functions
## scrollTo
The `scrollTo` function smoothly scrolls the page to a specified vertical position (pixel) over a given duration (duration). It uses an easing function (easeInOutQuad) to create a smooth scrolling animation.
### Parameters <!-- omit in toc -->
- pixel (number): The target vertical scroll position (in pixels) to scroll to. This value can be any integer representing the desired scroll position from the top of the page.
- duration (number): The duration (in milliseconds) over which the scroll animation should take place. This determines how quickly the page will scroll to the specified position.
### Easing Function (easeInOutQuad) <!-- omit in toc -->
The easing function Math.easeInOutQuad provides a smooth, non-linear transition, starting slowly, accelerating in the middle, and then decelerating towards the end. This creates a more natural, visually pleasing scroll animation.
### Usage Example <!-- omit in toc -->
```js
const scrollToPosition = 2000;
const scrollSpeed = 500;
scrollTo(scrollToPosition, scrollSpeed);
```
### Notes <!-- omit in toc -->
- The scroll position is updated for both document.documentElement and document.body to ensure compatibility across different browsers and document structures.
- The function creates a smooth scroll effect using the easeInOutQuad easing method.
- The scroll occurs asynchronously, with a timeout of 20ms between each update to the scroll position (increment).
- To stop or interrupt the scroll, you would need to add external logic to cancel the timeouts or animations.
### Potential Improvements <!-- omit in toc -->
If you need to ensure better performance, consider using requestAnimationFrame instead of setTimeout, which can optimize the animation for smoother performance, especially on devices with lower processing power.
## inViewport
Note: If you are using an SPA framework or library and your element is suddenly no longer observed, an adjustment to the function may be necessary. I have marked a TODO here, which you can activate as required.
The `inViewport` function is used to monitor an HTML element’s visibility in the viewport as the user scrolls. When the element enters or exits the viewport, the provided callback function is invoked. This can be useful for triggering certain actions when an element becomes visible, such as lazy loading content or triggering animations.
### Parameters <!-- omit in toc -->
- selector (string | HTMLElement):
- If a string is provided, it should be a CSS selector that will be used to find the element within the document.
- If an HTMLElement is provided, the function directly uses it to check for visibility.
- prefix (string): A unique string used as a prefix to avoid duplicate event listeners. It is also used to mark elements with a custom data attribute to track if the element has already been processed.
- callback (function): A function that will be invoked whenever the visibility status of the element changes (when it enters or exits the viewport). It is passed the current visibility status (true if in the viewport, false if not).
### Return Value <!-- omit in toc -->
The function does not return any value. Instead, it triggers the callback whenever the element enters or exits the viewport.
### Usage Example <!-- omit in toc -->
```js
inViewport('.my-element', 'elementPrefix', status => {
if (status) {
console.log('The element is now in the viewport!');
} else {
console.log('The element is no longer in the viewport!');
}
});
```
This will monitor the visibility of an element with the class .my-element. The callback will log a message when the element enters or exits the viewport.
```js
const myElement = qs('.my-element');
inViewport(myElement, 'elementPrefix', (status) => {
if (status) {
console.log('The element is now in the viewport!');
} else {
console.log('The element is no longer in the viewport!');
}
});
```
This will monitor the visibility of the myElement directly, using the provided HTMLElement.
### Notes <!-- omit in toc -->
- The function adds an event listener for the scroll event on the window, which may trigger frequently. If performance becomes an issue, consider throttling or debouncing the scroll event handler.
- This function is particularly useful in Single Page Applications (SPAs) or dynamic content sites, where elements may be loaded dynamically and visibility checks need to be performed after the element is rendered.
## isElementInViewport
The isElementInViewport function checks if an HTML element is currently visible within the viewport. It determines whether the element is fully or partially visible, and also checks if the element is positioned below the top of the viewport.
### Parameters <!-- omit in toc -->
- element (HTMLElement): The target HTML element to check for visibility within the viewport.
### Return Value <!-- omit in toc -->
Returns an object with two properties:
- below (boolean):
- true if the element is positioned below the top of the viewport.
- false if the element is above the top of the viewport.
- status (boolean):
- true if the entire element is visible within the viewport (the element’s top, bottom, left, and right edges must all be within the bounds of the viewport).
- false if any part of the element is outside the viewport.
If the element’s width and height are 0, the function will return undefined since the element is not considered visible.
### Usage Example <!-- omit in toc -->
```js
const element = qs('.my-element');
const visibility = isElementInViewport(element);
if (visibility && visibility.status) {
console.log('The element is fully visible in the viewport.');
} else {
console.log('The element is not fully visible in the viewport.');
}
```
Checks whether an element with the class .my-element is fully visible in the viewport and logs the result.
```js
const element = qs('.my-element');
const visibility = isElementInViewport(element);
if (visibility && visibility.below) {
console.log('The element is below the top of the viewport.');
} else {
console.log('The element is above the top of the viewport.');
}
```
Checks if the element is below the top of the viewport.
### Notes <!-- omit in toc -->
- Zero Dimensions: If the element has 0 width and 0 height, the function returns undefined as it cannot be considered visible.
- Edge Boundaries: The status will return false if any edge of the element is outside the viewport (even if only partially).
## isMobile
The `isMobile` function detects if the current user is accessing the website from a mobile device by examining the user agent string of the browser.
### Return Value <!-- omit in toc -->
- Boolean: true if the user agent string contains “android” or “mobile”, indicating a mobile device, false otherwise.
### Usage Example <!-- omit in toc -->
```js
if (isMobile()) {
console.log('User is on a mobile device.');
} else {
console.log('User is on a desktop or non-mobile device.');
}
```
### Notes <!-- omit in toc -->
- This function provides a simple check for mobile devices but may not cover all edge cases or device types.
- It may return true for some tablets or hybrid devices depending on their user agent strings.
- For more robust device detection, consider using dedicated libraries like Modernizr or Mobile Detect.
## getWidth
The `getWidth` function calculates and returns the maximum width of the current document, accounting for different properties that reflect the document’s width under various scenarios.
### Return Value <!-- omit in toc -->
- Number: The maximum width of the document in pixels.
### Usage Example <!-- omit in toc -->
```js
const pageWidth = getWidth();
console.log(`The current document width is ${pageWidth}px.`);
```
### Notes <!-- omit in toc -->
- This function is useful when needing to adapt UI elements or layouts based on the total document width.
- It accounts for varying browser implementations and conditions such as scrollbars or borders that might affect width calculations.
## setCookie
The `setCookie` function creates or updates a cookie with a specified name, value, and expiration date. It sets the cookie to be accessible across the entire website by using a path of /.
### Parameters <!-- omit in toc -->
- cookieName (string): The name of the cookie to be set.
- cookieValue (string): The value to be assigned to the cookie.
- expiryDays (number): The number of days until the cookie expires.
### Usage Example <!-- omit in toc -->
```js
// This code creates a cookie named username with the value JohnDoe that will expire in 7 days.
setCookie("username", "JohnDoe", 7);
```
### Notes <!-- omit in toc -->
- Cookies set by this function are accessible site-wide (path=/).
- Ensure cookie names are unique to avoid overwriting existing cookies.
## getCookie
[JavaScript Cookies](https://www.w3schools.com/js/js_cookies.asp)
The `getCookie` function retrieves the value of a specified cookie from the browser’s document.
### Parameters <!-- omit in toc -->
- cookieName (string):
The name of the cookie whose value you want to retrieve.
### Return Value <!-- omit in toc -->
- String: The value of the specified cookie.
- Returns an empty string ("") if the cookie does not exist.
### Usage Example <!-- omit in toc -->
```js
document.cookie = "username=JohnDoe";
const username = getCookie("username");
if (username) {
console.log(`Welcome back, ${username}!`);
} else {
console.log("No username cookie found.");
}
```
### Notes <!-- omit in toc -->
- The function assumes the cookie format follows the standard name=value convention.
- Special characters in cookie values are handled correctly due to decodeURIComponent().
## getStore
The getStore function retrieves data from a specified storage (localStorage or sessionStorage) and optionally returns a specific property of the stored data object. The function handles JSON parsing and gracefully manages errors if the stored data is not in a valid JSON format.
### Parameters <!-- omit in toc -->
- name (string): The key name under which the data is stored.
- key (string | null) [optional]: The property to retrieve from the stored object. If omitted or null, the entire object is returned.
- storage (string) [optional]: The storage type from which to retrieve the data. Options: "localStorage" (default) or "sessionStorage".
### Usage Example <!-- omit in toc -->
```js
const theme = getStore('userSettings', 'theme');
console.log(theme); // Outputs: 'dark'
```
Without a second parameter as a key, you get the entire data object back
```js
const data = getStore('myExampleObject');
```
## setStore
The setStore function saves or updates an object in client-side storage (localStorage or sessionStorage). It merges the new key-value pair with existing data stored under the specified name key, ensuring structured data persistence across user sessions.
### Parameters <!-- omit in toc -->
- name (string): The name of the storage key used to save or retrieve the data.
- key (string): The property name in the object to store or update.
- value (any): The value associated with the specified key.
- storage (string) [optional]: Specifies the storage type. Options are: "localStorage" (default) or "sessionStorage"
### Usage Example <!-- omit in toc -->
```js
setStore('userSettings', 'theme', 'dark');
```
This stores { theme: 'dark' } in localStorage under the key 'userSettings'.
```js
setStore('sessionData', 'isAuthenticated', true, 'sessionStorage');
```
This stores { isAuthenticated: true } in sessionStorage under the key 'sessionData'.
### Notes <!-- omit in toc -->
- The function relies on a properly implemented getStore() function to retrieve existing data.
- Data is stored as JSON strings and should be parsed back into objects when retrieved.
- Storage Limits: Both localStorage and sessionStorage typically have a 5MB storage limit.
- Best Practices: Avoid storing sensitive information in client-side storage for security reasons.
## setMutationObserver
The setMutationObserver function sets up a MutationObserver on a DOM element matching the given selector. The observer watches for mutations (changes) to the element, and when such changes occur, it triggers the provided callback function. This function returns a promise, resolving once the observer is successfully set, or rejecting if any errors occur.
### Parameters <!-- omit in toc -->
- selector (string): The CSS selector used to find the target DOM element to observe.
- prefix (string): A unique identifier used as a data attribute (data-[prefix]) to ensure that the observer is not added multiple times to the same element.
- callback (function): The callback function to be executed when mutations are detected on the target element.
- options (object): An optional object specifying which mutations should be observed. The default value is:
- attributes: Observe changes to the element’s attributes.
- childList: Observe changes to the child nodes of the element.
- subtree: Observe changes to the entire subtree of the element.
### Return Value <!-- omit in toc -->
Returns a Promise: Resolves when the mutation observer is successfully set up. Rejects if the provided callback is not a function or if any errors occur during the process.
### Usage Example <!-- omit in toc -->
```js
setMutationObserver('.example', 'experimentPrefix', () => {
console.log('Mutation detected on the element!');
});
```
### Notes <!-- omit in toc -->
- Element Selection: Uses elemSync(selector) to select the element synchronously. If the element is not found, the promise will reject.
- Observer Activation: The observer is activated only once for each element, as indicated by the data-[prefix] attribute.
- Error Handling: If the callback is not a function, or if an error occurs while setting up the observer, the promise is rejected with an error message.
- Mutation Types: By default, the observer listens for changes to attributes, child nodes, and the entire subtree. These can be customized via the options parameter.
## getPage
The `getPage` function determines the current page type based on the URL pathname. It optionally checks if the user is in the checkout process and categorizes the page accordingly. The function supports various page types, including home, product, search, brand, login, cart, and confirmation pages.
### Page Type Categories <!-- omit in toc -->
Page Type | Description | Condition
|---|---|---|
"home" | Homepage | Path matches regional homepage pattern
"pop" | Popular or category page | Path contains "/c/"
"pdp" | Product detail page (PDP) | Path contains "/p/"
"brand" | Brand-specific page | Path contains "/b/"
"search" | Search results page | Path contains "/search"
"login" | Login page | Path contains "/login"
"cart" | Cart page | Path contains "/cart"
"checkout" | Checkout process page (if inCheckout is true) | Path contains "/checkout/"
"confirm" | Post-checkout confirmation page | Path contains "/checkout/thankyou"
"no-checkout"| Non-checkout page when in checkout mode | Path doesn’t contain "/checkout/"
### Usage Example <!-- omit in toc -->
#### Determine Page Type <!-- omit in toc -->
```js
const pageType = getPage();
console.log("Current Page:", pageType); // e.g., "home" or "cart"
```
#### Check if in Checkout <!-- omit in toc -->
```js
const pageType = getPage(true);
console.log("Checkout Page Type:", pageType); // e.g., "checkout" or "no-checkout"
```
## pageChange
The `pageChange` function monitors changes in the browser’s URL without requiring a full page reload. It listens for changes triggered by pushState, replaceState, and popstate events, ensuring that a callback function is executed whenever the URL changes. This is particularly useful in Single Page Applications (SPAs) where navigation happens dynamically.
### Usage Example <!-- omit in toc -->
```js
pageChange('experimentPrefix', () => {
console.log('URL changed:', window.location.href);
});
```
```js
// Example of changing the URL
history.pushState({}, '', '/test'); // Logs: "URL changed: http://www.douglas.de/de/test"
```
## goTo
The `goTo` function is a utility designed to navigate to a specified URL using the System.history.history.push method, if available. It ensures that navigation attempts do not result in errors if System or its nested properties are undefined.
### Usage Example <!-- omit in toc -->
```js
// Navigating to a new page
const newPage = "/dashboard";
goTo(newPage);
```
## loadLibrary
The `loadLibrary` function dynamically loads an external JavaScript library from a given URL and ensures it is only loaded once.
If the script is already present in the document, it resolves immediately with the existing script element.
### Parameters
- `src` (`string`): The URL of the CDN-hosted library to load.
- `prefix` (`string`): A unique attribute name to prevent multiple initializations of the same script.
- `async` (`boolean`, optional): Whether to load the script asynchronously. Defaults to `true`.
### Returns
- `Promise<HTMLScriptElement>`: Resolves with the script element once loaded, or rejects if the loading fails.
### Usage Example <!-- omit in toc -->
```js
// Load Lodash from CDN
loadLibrary('https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js', 'experimentPrefix')
.then(() => {
// Now you can use _
console.log('Lodash loaded:', typeof _ !== 'undefined');
})
.catch(err => {
console.error('Failed to load library:', err);
});
```
## addStyleToBody
The `addStyleToBody` function injects a CSS string into the document head, using a unique prefix as a data attribute to prevent duplicate style insertions.
If a style element with the given prefix already exists, it does nothing. Otherwise, it creates and appends a new style element.
This function is especially useful as a CSS "trick" for A/B testing platforms like Kameleoon, where injected CSS may be removed when the experiment is deactivated, but JavaScript is still executed on the page. By injecting styles dynamically via JavaScript, you can ensure your experiment's styles persist as long as your script runs.
### Parameters
- `css` (`string`): The CSS string to be injected.
- `prefix` (`string`): The unique prefix used as a data attribute to identify the style element.
### Usage Example <!-- omit in toc -->
```js
// Inject custom minified styles for an experiment
const css = '.custom-selector{display: none !important;}';
addStyleToBody(css, 'experimentPrefix');
```
# Hotjar
The hotjar function triggers a Hotjar event for tracking user interactions by sending a unique event key to the Hotjar API. The function ensures that each event key is only triggered once per page load by maintaining a list of already transmitted keys.
### Parameters <!-- omit in toc -->
- key (string): Description: A unique identifier for the Hotjar event to be tracked.
### Usage Example <!-- omit in toc -->
```js
hotjar('your-event-key');
```
# License
Copyright (c) 2025 **PARFÜMERIE Douglas GmbH & CO. KG**.
Licensed under The [MIT License](./LICENSE.md) (MIT).
[**PARFÜMERIE Douglas GmbH & CO. KG**](https://www.douglas.de/)