UNPKG

@dotcms/analytics

Version:

Official JavaScript library for Content Analytics with DotCMS.

145 lines (144 loc) 5.32 kB
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 };