automatic-analytics-trigger
Version:
Automatically trigger events and mutations for analytics
184 lines (146 loc) • 5.45 kB
JavaScript
var defaultConfig = {
mutations: ['show'],
events: ['click'],
target: document.documentElement || document.body,
maxAncestorsChecks: 5,
};
const DATASET = 'data-analytics';
class EventListener {
constructor(events, target, dispatcheEvent, finder) {
this.target = target;
this.events = events;
this.dispatcheEvent = dispatcheEvent;
this.listeners = {};
this.finder = finder;
}
registerEventsListeners() {
for (const event of this.events) {
this.listeners[event] = e => this._eventHandler(e, `${DATASET}-${event}`);
this.target.addEventListener(event, this.listeners[event], false);
}
}
removeEventsListeners() {
for (const event of this.events) {
this.target.removeEventListener(event, this.listeners[event], false);
}
}
_eventHandler(event, datasetEvent) {
const markedElement = this.finder.firstWithAttribute(event, datasetEvent);
if (markedElement == undefined) return;
this.dispatcheEvent(markedElement);
}
}
class EventElementFinder {
constructor(maxAncestorsChecks = 0) {
this.maxAncestorsChecks = maxAncestorsChecks;
}
firstWithAttribute(event, attribute) {
let elements = event.composedPath();
elements = this._removeWindowAndDocumentFromList(elements);
elements = this._applyLimitIfNecessary(elements);
return elements.find(element => element.hasAttribute(attribute));
}
_removeWindowAndDocumentFromList(elements) {
return elements.slice(0, elements.length - 2);
}
_applyLimitIfNecessary(elements) {
if (this._notNecessaryApplyLimit(this.maxAncestorsChecks)) {
return elements;
}
return elements.slice(0, this.maxAncestorsChecks);
}
_notNecessaryApplyLimit(elements) {
return this.maxAncestorsChecks == 0 || elements.length <= this.maxAncestorsChecks;
}
}
const DATASET$1 = 'data-analytics';
class MutationListener {
constructor(mutations, target, dispatcheEvent) {
this.target = target;
this.mutations = mutations;
this.dispatcheEvent = dispatcheEvent;
}
registerMutationsListeners() {
this.observer = new MutationObserver(this._mutationHandler.bind(this));
this.observer.observe(this.target, {
attributes: true,
childList: true,
subtree: true,
attributeFilter: ['style'],
});
}
removeMutationsListeners() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
}
_mutationHandler(mutations) {
for (let mutation of mutations) {
this._checkShowAttributes(mutation);
this._checkShowAddedNodes(mutation);
}
}
_checkShowAttributes(mutation) {
if (mutation.type === 'attributes' && isShowEvent(mutation.target) && mutation.attributeName === 'style') {
if (isVisibleElement(mutation.target)) this.dispatcheEvent(mutation.target);
}
}
_checkShowAddedNodes(mutation) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
for (let node of mutation.addedNodes) {
if (isShowEvent(node) && isVisibleElement(node)) this.dispatcheEvent(node);
}
}
}
}
const isShowEvent = node => node.dataset && node.hasAttribute([`${DATASET$1}-show`]);
const isVisibleElement = element => element.style.display != 'none' && element.style.visibility != 'hidden';
class AutomaticAnalyticsTrigger {
constructor(callback, customConfig) {
const config = { ...defaultConfig, ...customConfig };
this.callback = callback;
this.target = config.target;
this.events = config.events;
this.mutations = config.mutations;
this.maxAncestorsChecks = config.maxAncestorsChecks;
this.finder = new EventElementFinder(this.maxAncestorsChecks);
this._dispatcheEventData = this._dispatcheEventData.bind(this);
}
init() {
if (this.events) {
this.eventListener = new EventListener(this.events, this.target, this._dispatcheEventData, this.finder);
this.eventListener.registerEventsListeners();
}
if (this.mutations) {
this.mutationListener = new MutationListener(this.mutations, this.target, this._dispatcheEventData);
this.mutationListener.registerMutationsListeners();
}
}
close() {
if (this.events) this.eventListener.removeEventsListeners();
if (this.mutations) this.mutationListener.removeMutationsListeners();
}
_dispatcheEventData(target) {
const eventData = this._getEventDataFromDataAttributes(target.dataset);
this.callback(eventData);
}
_getEventDataFromDataAttributes(dataset) {
const dimensions = Object.keys(dataset)
.filter(key => key.includes('analyticsDimension'))
.reduce((obj, key) => {
obj[key.replace('analyticsDimension', 'dimension')] = dataset[key];
return obj;
}, {});
return {
...(dataset.analyticsEvent && { event: dataset.analyticsEvent }),
...(dataset.analyticsEventCategory && { eventCategory: dataset.analyticsEventCategory }),
...(dataset.analyticsEventAction && { eventAction: dataset.analyticsEventAction }),
...(dataset.analyticsEventLabel && { eventLabel: dataset.analyticsEventLabel }),
...(dataset.analyticsEventValue && { eventValue: dataset.analyticsEventValue }),
...(dataset.analyticsScreenName && { screenName: dataset.analyticsScreenName }),
...(Object.keys(dimensions).length > 0 && { eventDimensions: dimensions }),
};
}
}
export default AutomaticAnalyticsTrigger;