@dotcms/analytics
Version:
Official JavaScript library for Content Analytics with DotCMS.
145 lines (144 loc) • 5.32 kB
JavaScript
import { handleContentletClick as c } from "./dot-analytics.click.utils.js";
import { DEFAULT_CLICK_THROTTLE_MS as d } from "../../shared/constants/dot-analytics.constants.js";
import { createPluginLogger as h, isBrowser as l, INITIAL_SCAN_DELAY_MS as g, findContentlets as r, createContentletObserver as u } from "../../shared/utils/dot-analytics.utils.js";
class C {
constructor(t) {
this.config = t, this.mutationObserver = null, this.lastClickTime = { value: 0 }, this.subscribers = /* @__PURE__ */ new Set(), this.trackedElements = /* @__PURE__ */ new WeakSet(), this.elementHandlers = /* @__PURE__ */ new WeakMap(), this.logger = h("Click", t);
}
/**
* Subscribe to click events
* @param callback - Function called when click is detected
* @returns Subscription object with unsubscribe method
*/
onClick(t) {
return this.subscribers.add(t), {
unsubscribe: () => {
this.subscribers.delete(t);
}
};
}
/**
* Notifies all subscribers of a click event
* @param eventName - Name of the event (e.g., 'content_click')
* @param payload - Click event payload with content and element data
*/
notifySubscribers(t, e) {
this.subscribers.forEach((i) => i(t, e));
}
/**
* Initialize click tracking system
*
* Performs the following:
* - Validates browser environment
* - Scans for existing contentlets after a delay (100ms)
* - Sets up MutationObserver for dynamic content
*
* The delay allows React/Next.js to finish initial rendering
* before attaching listeners.
*/
initialize() {
if (!l()) {
this.logger.warn("No document, skipping");
return;
}
this.logger.debug("Plugin initializing"), typeof window < "u" && setTimeout(() => {
this.logger.debug("Running initial scan after timeout..."), this.findAndAttachListeners();
}, g), this.initializeMutationObserver(), this.logger.info("Plugin initialized");
}
/**
* Attach click listener to a contentlet container
*
* Skips if element already has a listener attached.
* The listener delegates to handleContentletClick which:
* - Finds clicked anchor/button elements
* - Extracts contentlet and element data
* - Applies throttling (300ms)
* - Notifies subscribers
*
* @param element - Contentlet container element to track
*/
attachClickListener(t) {
if (this.trackedElements.has(t)) {
const n = t.dataset.dotAnalyticsIdentifier || "unknown";
this.logger.debug(`Element ${n} already has listener, skipping`);
return;
}
if (!t.dataset.dotAnalyticsDomIndex) {
const n = r();
t.dataset.dotAnalyticsDomIndex = String(n.indexOf(t));
}
const e = (n) => {
this.logger.debug("Click handler triggered on contentlet"), c(
n,
t,
(a, s) => {
const o = Date.now();
o - this.lastClickTime.value < d || (this.lastClickTime.value = o, this.notifySubscribers(a, s), this.logger.info(
`Fired click event for ${s.content.identifier}`,
s
));
},
this.logger
);
};
t.addEventListener("click", e), this.trackedElements.add(t), this.elementHandlers.set(t, e);
const i = t.dataset.dotAnalyticsIdentifier || "unknown";
this.logger.log(`Attached listener to contentlet ${i}`, t);
}
/**
* Find and attach listeners to all contentlet elements in the DOM
*
* Scans the entire document for elements with the
* `.dotcms-analytics-contentlet` class and attaches click
* listeners if not already tracked.
*
* Called during initialization and whenever DOM mutations are detected.
*/
findAndAttachListeners() {
this.logger.debug("findAndAttachListeners called");
const t = r();
this.logger.debug(`Scanning... found ${t.length} contentlets`);
let e = 0;
t.forEach((i) => {
const n = !this.trackedElements.has(i);
this.attachClickListener(i), n && this.trackedElements.has(i) && e++;
}), e > 0 && this.logger.info(`Attached ${e} new click listeners`);
}
/**
* Initialize MutationObserver to detect new contentlet containers
* Uses same simple strategy as impression tracker - no complex filtering
*/
initializeMutationObserver() {
l() && (this.mutationObserver = u(() => {
this.findAndAttachListeners();
}), this.logger.info("MutationObserver enabled for click tracking"));
}
/**
* Remove all click listeners from tracked contentlets
*
* Iterates through all contentlet elements and removes their
* click event handlers, cleaning up WeakMap references.
*/
removeAllListeners() {
r().forEach((e) => {
const i = this.elementHandlers.get(e);
i && (e.removeEventListener("click", i), this.elementHandlers.delete(e));
});
}
/**
* Cleanup all resources used by the click tracker
*
* Performs:
* - Removes all event listeners from contentlets
* - Disconnects MutationObserver
* - Clears internal references
*
* Should be called when the plugin is disabled or on page unload.
*/
cleanup() {
this.removeAllListeners(), this.mutationObserver && (this.mutationObserver.disconnect(), this.mutationObserver = null), this.logger.info("Click tracking cleaned up");
}
}
export {
C as DotCMSClickTracker
};