analytics/tracker/src/customEvents.js
Vini Brasil 67be43b6e6
Add tagged events support for revenue goals (#3038)
* Add tagged events support for revenue goals

This commit adds two special classes for tracking revenue with tagged
events. The following example sends an event with revenue data when the
tracker script has revenue and tagged-events script extensions:

```html
<button class="plausible-event-revenue-amount=10.29 plausible-event-revenue-currency=EUR"></button>
```

* Rename special revenue tracking class name
2023-06-19 12:59:47 +01:00

229 lines
6.7 KiB
JavaScript

function getLinkEl(link) {
while (link && (typeof link.tagName === 'undefined' || !isLink(link) || !link.href)) {
link = link.parentNode
}
return link
}
function isLink(element) {
return element && element.tagName && element.tagName.toLowerCase() === 'a'
}
function shouldFollowLink(event, link) {
// If default has been prevented by an external script, Plausible should not intercept navigation.
if (event.defaultPrevented) { return false }
var targetsCurrentWindow = !link.target || link.target.match(/^_(self|parent|top)$/i)
var isRegularClick = !(event.ctrlKey || event.metaKey || event.shiftKey) && event.type === 'click'
return targetsCurrentWindow && isRegularClick
}
var MIDDLE_MOUSE_BUTTON = 1
function handleLinkClickEvent(event) {
if (event.type === 'auxclick' && event.button !== MIDDLE_MOUSE_BUTTON) { return }
var link = getLinkEl(event.target)
var hrefWithoutQuery = link && link.href && link.href.split('?')[0]
{{#if tagged_events}}
if (isElementOrParentTagged(link, 0)) {
// Return to prevent sending multiple events with the same action.
// Clicks on tagged links are handled by another function.
return
}
{{/if}}
{{#if outbound_links}}
if (isOutboundLink(link)) {
return sendLinkClickEvent(event, link, { name: 'Outbound Link: Click', props: { url: link.href } })
}
{{/if}}
{{#if file_downloads}}
if (isDownloadToTrack(hrefWithoutQuery)) {
return sendLinkClickEvent(event, link, { name: 'File Download', props: { url: hrefWithoutQuery } })
}
{{/if}}
}
function sendLinkClickEvent(event, link, eventAttrs) {
var followedLink = false
function followLink() {
if (!followedLink) {
followedLink = true
window.location = link.href
}
}
if (shouldFollowLink(event, link)) {
var attrs = { props: eventAttrs.props, callback: followLink }
{{#if revenue}}
attrs.revenue = eventAttrs.revenue
{{/if}}
plausible(eventAttrs.name, attrs)
setTimeout(followLink, 5000)
event.preventDefault()
} else {
var attrs = { props: eventAttrs.props }
{{#if revenue}}
attrs.revenue = eventAttrs.revenue
{{/if}}
plausible(eventAttrs.name, attrs)
}
}
document.addEventListener('click', handleLinkClickEvent)
document.addEventListener('auxclick', handleLinkClickEvent)
{{#if outbound_links}}
function isOutboundLink(link) {
return link && link.href && link.host && link.host !== location.host
}
{{/if}}
{{#if file_downloads}}
var defaultFileTypes = ['pdf', 'xlsx', 'docx', 'txt', 'rtf', 'csv', 'exe', 'key', 'pps', 'ppt', 'pptx', '7z', 'pkg', 'rar', 'gz', 'zip', 'avi', 'mov', 'mp4', 'mpeg', 'wmv', 'midi', 'mp3', 'wav', 'wma']
var fileTypesAttr = scriptEl.getAttribute('file-types')
var addFileTypesAttr = scriptEl.getAttribute('add-file-types')
var fileTypesToTrack = (fileTypesAttr && fileTypesAttr.split(",")) || (addFileTypesAttr && addFileTypesAttr.split(",").concat(defaultFileTypes)) || defaultFileTypes;
function isDownloadToTrack(url) {
if (!url) { return false }
var fileType = url.split('.').pop();
return fileTypesToTrack.some(function (fileTypeToTrack) {
return fileTypeToTrack === fileType
})
}
{{/if}}
{{#if tagged_events}}
// Finds event attributes by iterating over the given element's (or its
// parent's) classList. Returns an object with `name` and `props` keys.
function getTaggedEventAttributes(htmlElement) {
var taggedElement = isTagged(htmlElement) ? htmlElement : htmlElement && htmlElement.parentNode
var eventAttrs = { name: null, props: {} }
{{#if revenue}}
eventAttrs.revenue = {}
{{/if}}
var classList = taggedElement && taggedElement.classList
if (!classList) { return eventAttrs }
for (var i = 0; i < classList.length; i++) {
var className = classList.item(i)
var matchList = className.match(/plausible-event-(.+)(=|--)(.+)/)
if (matchList) {
var key = matchList[1]
var value = matchList[3].replace(/\+/g, ' ')
if (key.toLowerCase() == 'name') {
eventAttrs.name = value
} else {
eventAttrs.props[key] = value
}
}
{{#if revenue}}
var revenueMatchList = className.match(/plausible-revenue-(.+)(=|--)(.+)/)
if (revenueMatchList) {
var key = revenueMatchList[1]
var value = revenueMatchList[3]
eventAttrs.revenue[key] = value
}
{{/if}}
}
return eventAttrs
}
function handleTaggedFormSubmitEvent(event) {
var form = event.target
var eventAttrs = getTaggedEventAttributes(form)
if (!eventAttrs.name) { return }
event.preventDefault()
var formSubmitted = false
function submitForm() {
if (!formSubmitted) {
formSubmitted = true
form.submit()
}
}
setTimeout(submitForm, 5000)
var attrs = { props: eventAttrs.props, callback: submitForm }
{{#if revenue}}
attrs.revenue = eventAttrs.revenue
{{/if}}
plausible(eventAttrs.name, attrs)
}
function isForm(element) {
return element && element.tagName && element.tagName.toLowerCase() === 'form'
}
var PARENTS_TO_SEARCH_LIMIT = 3
function handleTaggedElementClickEvent(event) {
if (event.type === 'auxclick' && event.button !== MIDDLE_MOUSE_BUTTON) { return }
var clicked = event.target
var clickedLink
var taggedElement
// Iterate over parents to find the tagged element. Also search for
// a link element to call for different tracking behavior if found.
for (var i = 0; i <= PARENTS_TO_SEARCH_LIMIT; i++) {
if (!clicked) { break }
// Clicks inside forms are not tracked. Only form submits are.
if (isForm(clicked)) { return }
if (isLink(clicked)) { clickedLink = clicked }
if (isTagged(clicked)) { taggedElement = clicked }
clicked = clicked.parentNode
}
if (taggedElement) {
var eventAttrs = getTaggedEventAttributes(taggedElement)
if (clickedLink) {
eventAttrs.props.url = clickedLink.href
sendLinkClickEvent(event, clickedLink, eventAttrs)
} else {
var attrs = {}
attrs.props = eventAttrs.props
{{#if revenue}}
attrs.revenue = eventAttrs.revenue
{{/if}}
plausible(eventAttrs.name, attrs)
}
}
}
function isTagged(element) {
var classList = element && element.classList
if (classList) {
for (var i = 0; i < classList.length; i++) {
if (classList.item(i).match(/plausible-event-name(=|--)(.+)/)) { return true }
}
}
return false
}
function isElementOrParentTagged(element, parentsChecked) {
if (!element || parentsChecked > PARENTS_TO_SEARCH_LIMIT) { return false }
if (isTagged(element)) { return true }
return isElementOrParentTagged(element.parentNode, parentsChecked + 1)
}
document.addEventListener('submit', handleTaggedFormSubmitEvent)
document.addEventListener('click', handleTaggedElementClickEvent)
document.addEventListener('auxclick', handleTaggedElementClickEvent)
{{/if}}