Spaces:
No application file
No application file
(function(window, document, Mautic, $, Math) { | |
class Heatmap { | |
constructor(emailId) { | |
this.emailId = emailId; | |
this.mode = 'total'; | |
this.content = null; | |
this.clickStats = null; | |
this.$modal = null; | |
this.$iframe = null; | |
this.$iframeBody = null; | |
this.iframeDocument = null; | |
this.totalClicks = null; | |
this.totalUniqueClicks = null; | |
this.legendTemplate = null; | |
this.links = []; | |
this.gradient = [ | |
[44, 59, 182], // #2c3bb6 | |
[10, 133, 255], // #0a85ff | |
[240, 223, 66], // #f0df42 | |
[248, 195, 68], // #f8c344 | |
[255, 132, 58], // #ff843a | |
[248, 56, 52] // #f83834 | |
]; | |
} | |
init() { | |
this.fetchHeatmap(function() { | |
this.render(); | |
}.bind(this)); | |
} | |
render() { | |
this.renderModal(); | |
this.bindEvents(); | |
this.$modal.modal('show'); | |
} | |
fetchHeatmap(callback) { | |
Mautic.ajaxActionRequest('email:heatmap', {id: this.emailId}, function(response){ | |
this.content = response.content; | |
this.clickStats = response.clickStats; | |
this.totalClicks = response.totalClicks; | |
this.totalUniqueClicks = response.totalUniqueClicks; | |
this.legendTemplate = response.legendTemplate; | |
callback(); | |
}.bind(this), false, true, "GET"); | |
} | |
waitForIframeContent(callback) { | |
const self = this; | |
const interval = setInterval(function () { | |
const height = self.$iframeBody.height(); | |
if (height > 0 && self.lastHeight === height) { | |
callback(); | |
clearInterval(interval); | |
} else { | |
self.lastHeight = height; | |
} | |
}, 100); | |
} | |
bindEvents() { | |
const self = this; | |
self.$iframe[0].addEventListener('load', function() { | |
self.waitForIframeContent(function() { | |
self.renderLabels(); | |
self.bindMouseEvents(); | |
}); | |
}); | |
$(window).on('resize', function() { | |
self.labelPositions(); | |
}); | |
self.$modal.on('hidden.bs.modal', function () { | |
$(this).remove(); | |
}); | |
$('[data-toggle="heatmap-total"]').click(function(e) { | |
e.preventDefault(); | |
if (self.mode === 'total') return; | |
self.mode = 'total'; | |
$('[data-heatmap-clicks]').html(self.totalClicks); | |
$('[data-toggle="heatmap-unique"]').removeClass('active'); | |
$(this).addClass('active'); | |
self.removeLabels(); | |
self.renderLabels(); | |
}); | |
$('[data-toggle="heatmap-unique"]').click(function(e) { | |
e.preventDefault(); | |
if (self.mode === 'unique') return; | |
self.mode = 'unique'; | |
$('[data-heatmap-clicks]').html(self.totalUniqueClicks); | |
$('[data-toggle="heatmap-total"]').removeClass('active'); | |
$(this).addClass('active'); | |
self.removeLabels(); | |
self.renderLabels(); | |
}); | |
$('div.heatmap-legend').on('scroll mousewheel touchmove', function(e) { | |
e.preventDefault(); | |
}); | |
} | |
bindMouseEvents() { | |
const self = this; | |
const moveUp = function() { | |
const $label = $(this).hasClass('heatmap-link') ? $(this).data('heatmap-label') : $(this); | |
$label.css('z-index', 2050); | |
} | |
const moveDown = function() { | |
const $label = $(this).hasClass('heatmap-link') ? $(this).data('heatmap-label') : $(this); | |
$label.css('z-index', 1050); | |
} | |
self.$iframeBody.on('mouseenter focus', '.heatmap-label, a.heatmap-link', moveUp); | |
self.$iframeBody.on('mouseleave blur', '.heatmap-label, a.heatmap-link', moveDown); | |
} | |
renderModal() { | |
this.$modal = $("<div />").attr({"class": "modal fade heatmap-modal"}); | |
const $modalDialogDiv = $("<div />").attr({"class": "modal-dialog modal-dialog-heatmap"}); | |
const $modalContentDiv = $("<div />").attr({"class": "modal-content"}); | |
this.$iframe = $('<iframe class="heatmap-iframe">' + this.content + '</iframe>'); | |
$modalContentDiv.append(this.$iframe); | |
this.$modal.append($modalDialogDiv.append($modalContentDiv)); | |
$('body').append(this.$modal); | |
this.iframeDocument = this.$iframe[0].contentDocument || this.$iframe[0].contentWindow.document; | |
this.iframeDocument.open(); | |
this.iframeDocument.write(this.content); | |
const cssLink = document.createElement("link"); | |
cssLink.href = "/app/bundles/EmailBundle/Assets/css/heatmap.css"; | |
cssLink.rel = "stylesheet"; | |
cssLink.type = "text/css"; | |
this.iframeDocument.head.appendChild(cssLink); | |
this.$iframeBody = $('body', this.iframeDocument); | |
this.$iframeBody.addClass('heatmap-iframe-body'); | |
$modalContentDiv.append(this.legendTemplate); | |
$modalContentDiv.append('<button type="button" class="modal-heatmap-close close" data-dismiss="modal"><span aria-hidden="true">×</span></button>'); | |
this.iframeDocument.close(); | |
} | |
renderLabels() { | |
const self = this; | |
self.clickStats.forEach(function(link) { | |
const $a = $('a[href="' + link.url + '"]', self.$iframeBody); | |
$a.addClass('heatmap-link'); | |
$a.each(function() { | |
const $el = $(this); | |
self.links.push($el); | |
const rate = self.mode === 'total' ? link.hits_rate : link.unique_hits_rate; | |
const percent = Math.round(rate * 100); | |
const text = (self.mode === 'total' ? link.hits_text : link.unique_hits_text) + ' (' + percent.toString() + '%)'; | |
const $label = $('<div class="heatmap-label"><p>' + text + '</p></div>'); | |
const bgColor = self.interpolateColor(rate); | |
const bgColorLeft = self.interpolateColor(rate - 0.1); | |
const bgColorRight = self.interpolateColor(rate + 0.1); | |
$label.css({ | |
'background-color': bgColor, | |
'background': 'linear-gradient(to right, ' +bgColorLeft+ ', '+ bgColorRight +')' | |
}); | |
const $border = $('<div class="heatmap-label-border"></div>'); | |
$border.css({ | |
'border': '1px dashed ' + bgColor, | |
'border-bottom': 'none' | |
}); | |
$label.append($border); | |
$label.attr('title', link.url); | |
self.$iframeBody.append($label); | |
$el.data('heatmap-label', $label); | |
$el.data('heatmap-label-border', $border); | |
$label.data('a', $a); | |
}); | |
}); | |
self.labelPositions(); | |
} | |
removeLabels() { | |
if (!this.links.length) return; | |
$(this.links).each(function() { | |
$(this).data('heatmap-label').remove(); | |
}); | |
this.links = []; | |
} | |
labelPositions() { | |
const self = this; | |
$(self.links).each(function() { | |
const $el = $(this); | |
const $label = $el.data('heatmap-label'); | |
const $border = $el.data('heatmap-label-border'); | |
const position = $el.position(); | |
$label.css({ | |
position: 'absolute', | |
top: position.top + $el.outerHeight(), | |
left: position.left - 1, | |
'min-width': Math.max($el.outerWidth(), 60) + 2 | |
}); | |
$border.css({ | |
position: 'absolute', | |
bottom: '100%', | |
left: 0, | |
width: $el.outerWidth(), | |
height: $el.outerHeight() | |
}); | |
}); | |
} | |
interpolateColor(rate) { | |
if (rate <= 0) { | |
return 'rgb(' + this.gradient[0].join(',') + ')'; | |
} | |
if (rate >= 1) { | |
const lastIndex = this.gradient.length - 1; | |
return 'rgb(' + this.gradient[lastIndex].join(',') + ')'; | |
} | |
const segmentCount = this.gradient.length - 1; | |
const segmentWidth = 1 / segmentCount; | |
const segmentIndex = Math.floor(rate / segmentWidth); | |
const segmentPercent = (rate - segmentIndex * segmentWidth) / segmentWidth; | |
const color1 = this.gradient[segmentIndex]; | |
const color2 = this.gradient[segmentIndex + 1]; | |
const r = Math.round(color1[0] + (color2[0] - color1[0]) * segmentPercent); | |
const g = Math.round(color1[1] + (color2[1] - color1[1]) * segmentPercent); | |
const b = Math.round(color1[2] + (color2[2] - color1[2]) * segmentPercent); | |
return 'rgb(' + r + ',' + g + ',' + b + ')'; | |
} | |
} | |
$(document).ready(function() { | |
$('body').on('click', '[data-toggle="email-heatmap"]', function(e) { | |
const emailId = $(this).data('email'); | |
const heatmap = new Heatmap(emailId); | |
heatmap.init(); | |
e.preventDefault(); | |
}); | |
}); | |
})(window, document, Mautic, mQuery, Math); | |