/**
* Personalize the page.
*
* This is done by using the options object as a blueprint for what attributes to set on which DOM nodes.
*
* @example Set a `src` attribute on an `img` tag
*
* window.lr_analytics.personalizePage({
* items: [
* {
* selector: 'img',
* attribute: 'src',
* source: 'https://example.org/img'
* }
* ]
* });
*
* @example Remove the `hidden` attribute on a DOM node
* window.lr_analytics.personalizePage({
* items: [
* {
* selector: 'div',
* attribute: 'hidden',
* source: false
* }
* ]
* });
*
* @example Add a css class to a DOM node after modifying it
* window.lr_analytics.personalizePage({
* items: [
* {
* selector: '#personalize-link',
* attribute: 'href',
* source: 'https://some-new-link.com',
* addClass: ['highlight']
* }
* ]
* });
*
*
* @param {Object} options - Object specifying how to personalize the page.
* @param {Object[]} options.items - List of DOM nodes whose content we want to swap out.
* @param {string} options.items[].selector - CSS selector for the node
* @param {*} options.items[].source - The value to set the attribute to. If it is a function, it will be evaluated and the result will be used
* @param {string} options.items[].attribute - The name of the node attribute to set (e.g. `src` for images and scripts).
* @param {string[]} options.items[].addClass - List of css classes to add to the node.
* @param {string[]} options.items[].removeClass - List of css classes to remove from the node.
*/
export function personalizePage({ items }) {
if (!items) {
throw new Error("`options.items` is required");
}
if (!Array.isArray(items)) {
throw new Error("`options.items` must be an array");
}
// see the eslint no-prototype-builtin rule for the reason behind calling `hasOwnProperty` in
// such an odd manner
if (
!(
items.every((x) => Object.prototype.hasOwnProperty.call(x, "selector")) &&
items.every((x) => Object.prototype.hasOwnProperty.call(x, "source")) &&
items.every((x) => Object.prototype.hasOwnProperty.call(x, "attribute"))
)
) {
throw new Error(
"one of the objects is `options.items` has an invalid shape",
);
}
for (const item of items) {
// This will only throw if the selector is invalid.
const node = document.querySelector(item.selector);
if (!node) {
// skip personalization if we can't find the node
return;
}
if (typeof item.source === "function") {
node[item.attribute] = item.source();
} else {
node[item.attribute] = item.source;
}
const { addClass = [], removeClass = [] } = item;
// Personalizing a page sometimes invovles adding/removing css classes to indicate
// that a node has been personalized
node.classList.add(...addClass);
node.classList.remove(...removeClass);
}
}