Commit 5b066770 by James Ooi

Refactor Foresight into TypeScript

parent 6ebb1c92
......@@ -18,7 +18,10 @@
</head>
<body>
<div style="background-color: #FAFAFA; min-height: 100vh; display: flex; justify-content: center; align-items: center">
<div
style="background-color: #FAFAFA; min-height: 100vh; display: flex; justify-content: center; align-items: center"
data-track-view="views;landing : view : google"
>
<a href="https://google.com" class="btn btn-primary" data-track="clicks;landing : all models : series select">
Google
</a>
......@@ -34,12 +37,23 @@
</a>
</div>
<div
style="background-color: #FFFFFF; min-height: 100vh; display: flex; justify-content: center; align-items: center"
data-track="landing : section : twitter"
data-track-view="views;landing : view : twitter"
data-track-view:interaction
>
<a href="https://twitter.com" class="btn btn-secondary" data-track="clicks;landing" data-track:non-interaction>
Twitter
</a>
</div>
</body>
<script src="build/foresight.js"></script>
<script>
Foresight.track();
new Foresight().start();
</script>
</html>
\ No newline at end of file
......@@ -11,10 +11,11 @@
"webpack-cli": "^3.1.0"
},
"dependencies": {
"intersection-observer": "^0.5.0",
"scrollmonitor": "^1.2.4"
},
"scripts": {
"start": "webpack --watch",
"start": "webpack --watch & browser-sync start -s . -f .",
"dist": "webpack -p --progress"
}
}
/**
* Foresight Event Tracking Library
* Foresight
*
* @author James Ooi <james.ooi@forefront.com.my>
* @license MIT
* @copyright 2018 (c) FOREFRONT International Sdn Bhd
*/
// import ScrollMonitor from 'scrollmonitor';
import 'intersection-observer';
import * as Utils from './utils';
declare var gtag: (action: string, ...args: any[]) => any;
/**
* Available options for configuring Foresight.
*/
interface ForesightConfig {
observerOptions?: IntersectionObserverInit
nonInteractionClicks?: boolean
nonInteractionViews?: boolean
}
/**
* Foresight is an event analytics tracking library that allows for declarative
* and elegant event tracking.
* Represents a particular event's data.
*/
interface EventData {
category: string
action: string
label: string
interaction: boolean
}
/**
* Foresight is an analytics library that allows for declarative event tracking
* in your websites.
*/
class Foresight {
static track() {
console.log('hellu!');
/**
* Default Options
* @static
*/
static defaultOptions: Partial<ForesightConfig> = {
nonInteractionClicks: false,
nonInteractionViews: true,
observerOptions: {},
}
/**
* Stores the options of the current Foresight instance.
* @public
*/
options: ForesightConfig;
/**
* Stores a mapping of elements with is respective functions to de-register
* listeners.
* @private
*/
private _untrackFns: Map<Element, { click: Function, view: Function }> = new Map();
/**
* Stores an instance of an IntersectionObserver.
* @private
*/
private _observer: IntersectionObserver = null;
/**
* @constructor Foresight
*/
constructor(config: ForesightConfig) {
this.options = { ...Foresight.defaultOptions, ...config };
// Initialise IntersectionObserver
this._observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this._onTrackedView(entry.target, observer);
observer.unobserve(entry.target);
}
});
}, this.options.observerOptions);
}
/**
* Start event tracking for all DOM elements with event tracking attributes.
*/
start(root: Element = document.body) {
if (gtag === undefined) {
throw `The 'gtag' function is undefined. Has Google Analytics been loaded yet?`;
}
Utils
.toArray<Element>(root.querySelectorAll('[data-track], [data-track-view]'))
.map(element => this.track(element));
}
/**
* Enables event tracking for a DOM element with event tracking attribute.
*/
track(element: Element) {
if (!this._untrackFns.has(element)) {
this._untrackFns.set(element, { click: null, view: null });
}
const untrackFn = this._untrackFns.get(element);
// Track clicks
if (element.getAttribute('data-track') !== null) {
if (untrackFn.click == null) {
untrackFn.click = this._trackClicks(element);
}
}
// Track views
if (element.getAttribute('data-track-view') !== null) {
if (untrackFn.view == null) {
untrackFn.view = this._trackViews(element);
}
}
this._untrackFns.set(element, untrackFn);
}
/**
* Disable event tracking for a DOM element.
*/
untrack(element: Element) {
const untrackFn = this._untrackFns.get(element);
if (untrackFn === undefined) {
return;
}
if (untrackFn.click !== null) {
untrackFn.click();
}
if (untrackFn.view !== null) {
untrackFn.view();
}
this._untrackFns.delete(element);
}
/**
* Parse an event string and returns a `EventData` object.
*/
parseEventString(eventString: string): EventData {
const split = eventString.split(';');
let [ category, action, label ] = split;
// If only one argument is provided, then the argument is the action
if (split.length === 1) {
action = category;
category = undefined;
}
return { category, action, label, interaction: true }
}
/**
* Registers click listeners that triggers an analytics event when the element
* is clicked or middle clicked.
*
* @returns Returns a function to remove the event listener.
*/
private _trackClicks(element: Element): Function {
// Define listen fucntion
const listener = (e) => {
this._onTrackedClick(element, e);
};
element.addEventListener('click', listener);
element.addEventListener('auxclick', listener);
// Define unlisten function
const unlisten = () => {
element.removeEventListener('click', listener);
element.removeEventListener('auxclick', listener);
}
return unlisten;
}
/**
* Registers a view observer that triggers an analytics event when the element
* is in view.
*
* @returns Returns a function that disconnects the view observer.
*/
private _trackViews(element: Element): Function {
this._observer.observe(element);
return () => this._observer.unobserve(element);
}
/**
* @private
* Handles a click event on an element that is being tracked by Foresight.
*/
private _onTrackedClick(element: Element, event: Event) {
const s = element.getAttribute('data-track');
const data = this.parseEventString(s);
if (element.getAttribute('data-track:non-interaction') !== null) {
data.interaction = false;
}
gtag('event', data.action, {
'event_label': data.label,
'event_category': data.category,
'non_interaction': !data.interaction,
});
}
/**
* @private
* Handles a view event on an element that is being tracked by Foresight.
*/
private _onTrackedView(element: Element, observer) {
const s = element.getAttribute('data-track-view');
const data = this.parseEventString(s);
if (element.getAttribute('data-track-view:interaction') !== null) {
data.interaction = true;
} else {
data.interaction = false;
}
gtag('event', data.action, {
'event_label': data.label,
'event_category': data.category,
'non_interaction': !data.interaction,
});
}
// static track() {
// if (gtag === undefined) {
// throw 'The `gtag` function is undefined. Has Google Analytics been loaded yet?';
// }
// Foresight._setupClickTrackers();
// Foresight._setupViewTrackers();
// }
// static _setupClickTrackers() {
// const elements = [].slice
// .call(document.querySelectorAll('[data-track]'))
// .filter(el => el.getAttribute('data-track-ready') === null)
// .forEach(el => {
// el.addEventListener('click', e => Foresight._onClick(el, e));
// el.addEventListener('auxclick', e => Foresight._onClick(el, e)); // also track middle clicks
// el.setAttribute('data-track-ready', '');
// });
// }
// static _setupViewTrackers() {
// const watchers = [].slice
// .call(document.querySelectorAll('[data-track-view]'))
// .filter(el => el.getAttribute('data-track-view-ready') === null)
// .map(el => {
// const watcher = ScrollMonitor.create(el);
// watcher.enterViewport(() => {
// Foresight._onView(el);
// watcher.destroy();
// });
// el.setAttribute('data-track-view-ready', '');
// return watcher;
// });
// }
// static _onClick(element) {
// const eventString = element.getAttribute('data-track');
// if (eventString === null || eventString === '') {
// console.warn('Insufficient tracking arguments provided.', element);
// return;
// }
// const eventStringArr = eventString.split(';');
// let [ eventCategory, eventName, eventLabel ] = eventStringArr;
// let isNonInteraction = element.getAttribute('data-track:non-interaction') !== null;
// // If only one argument is provided, then the argument is the action
// if (eventStringArr.length === 1) {
// eventName = eventCategory;
// eventCategory = undefined;
// }
// gtag('event', eventName, {
// 'event_label': eventLabel,
// 'event_category': eventCategory,
// 'non_interaction': isNonInteraction,
// });
// }
// static _onView(element) {
// const eventString = element.getAttribute('data-track-view');
// if (eventString === null || eventString === '') {
// console.warn('Insufficient tracking arguments provided.', element);
// return;
// }
// const eventStringArr = eventString.split(';');
// let [ eventCategory, eventName, eventLabel ] = eventStringArr;
// let isNonInteraction = !(element.getAttribute('data-track-view:is-interaction') !== null);
// // If only one argument is provided, then the argument is the action
// if (eventStringArr.length === 1) {
// eventName = eventCategory;
// eventCategory = undefined;
// }
// gtag('event', eventName, {
// 'event_label': eventLabel,
// 'event_category': eventCategory,
// 'non_interaction': isNonInteraction,
// });
// }
}
export = Foresight;
export { default as toArray } from './toArray';
\ No newline at end of file
/**
* Creates a new `Array` instance from an array-like or iterable object.
*/
export default function toArray<T>(arrayLike: any): T[] {
return [].slice.call(arrayLike);
}
......@@ -2,8 +2,9 @@
"compilerOptions": {
"target": "es5",
"allowJs": true,
"noImplicitAny": true,
"removeComments": true
"noImplicitAny": false,
"removeComments": true,
"lib": ["es6", "dom"]
},
"exclude": [
"node_modules",
......
......@@ -1187,6 +1187,10 @@ interpret@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
intersection-observer@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.5.0.tgz#9fe8bee3953c755b1485c38efd9633d535775ea6"
invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment