Thanks for testing out the Alpha version of the slide-in survey. This is the first version, so it might have some bugs. Be sure to report them so I can improve the script.
To get started, you’ll need publish access to GTM and admin access to GA4.
This tool was created to easily collect user feedback without having to buy another tool that slows down your website.
The idea came about because I, Sander Volbeda, missed the feedback feature from Hotjar while using Microsoft Clarity for heatmap and recordings tracking.
This is what it looks like:
Be aware, GA4 will only collect up to 100 characters. This includes spaces.
Before you drop the code into your GTM container, it’s good to know something about the triggering.
This slide-in survey triggers when someone is on the page for 3 seconds. Once the survey is completed or closed, it won’t show again for 30 days. This information is stored in the local storage of the user’s browser.
<script>
(function() {
'use strict';
var lastClosed = localStorage.getItem('slideInLastClosed');
var THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;
var now = new Date().getTime();
if (lastClosed && now - lastClosed < THIRTY_DAYS) {
return;
}
var container = document.createElement('div');
container.style.position = 'fixed';
container.style.zIndex = '9999999999';
container.style.right = '-320px';
container.style.top = '50%';
container.style.transform = 'translateY(-50%)';
container.style.width = '100%';
container.style.maxWidth = '320px';
container.style.boxSizing = 'border-box';
container.style.padding = '20px';
container.style.backgroundColor = '#ffffff';
container.style.boxShadow = '0 0 10px rgba(0,0,0,0.1)';
container.style.borderRadius = '10px 0 0 10px';
container.style.fontFamily = 'Arial, sans-serif';
container.style.transition = 'right 0.3s ease-in-out';
document.body.appendChild(container);
var closeButton = document.createElement('button');
closeButton.innerHTML = '×';
closeButton.style.position = 'absolute';
closeButton.style.right = '10px';
closeButton.style.top = '10px';
closeButton.style.background = 'none';
closeButton.style.border = 'none';
closeButton.style.fontSize = '24px';
closeButton.style.cursor = 'pointer';
closeButton.addEventListener('click', function() {
container.style.right = '-320px';
localStorage.setItem('slideInLastClosed', new Date().getTime());
window.dataLayer.push({
event: 'slide_in_closed',
slide_in_closed: 'true'
});
});
container.appendChild(closeButton);
var question1 = document.createElement('p');
question1.textContent = 'Do you enjoy using our product?';
question1.style.fontWeight = 'bold';
question1.style.marginBottom = '15px';
container.appendChild(question1);
var radioYes = createRadioButton('q1', 'yes', 'Yes');
var radioNo = createRadioButton('q1', 'no', 'No');
container.appendChild(radioYes);
container.appendChild(radioNo);
var question2 = document.createElement('div');
question2.style.display = 'none';
container.appendChild(question2);
var submitButton = document.createElement('button');
submitButton.textContent = 'Submit Feedback';
submitButton.style.width = '100%';
submitButton.style.display = 'none';
submitButton.style.marginTop = '15px';
submitButton.style.padding = '10px 20px';
submitButton.style.backgroundColor = '#4CAF50';
submitButton.style.color = 'white';
submitButton.style.border = 'none';
submitButton.style.borderRadius = '5px';
submitButton.style.cursor = 'pointer';
container.appendChild(submitButton);
radioYes.querySelector('input').addEventListener('change', function() {
handleQ1Answer('yes');
});
radioNo.querySelector('input').addEventListener('change', function() {
handleQ1Answer('no');
});
submitButton.addEventListener('click', handleSubmit);
function createRadioButton(name, value, label) {
var wrapper = document.createElement('label');
wrapper.style.display = 'block';
wrapper.style.marginBottom = '10px';
wrapper.style.cursor = 'pointer';
var radio = document.createElement('input');
radio.type = 'radio';
radio.name = name;
radio.value = value;
radio.style.marginRight = '10px';
var labelText = document.createTextNode(label);
wrapper.appendChild(radio);
wrapper.appendChild(labelText);
return wrapper;
}
function handleQ1Answer(answer) {
window.dataLayer.push({
event: 'question_1_answered',
question_1_answered: answer
});
question1.style.display = 'none';
radioYes.style.display = 'none';
radioNo.style.display = 'none';
question2.innerHTML = '';
question2.style.display = 'block';
submitButton.style.display = 'block';
var q2Text = document.createElement('p');
q2Text.style.fontWeight = 'bold';
q2Text.style.marginBottom = '15px';
if (answer === 'yes') {
q2Text.textContent = 'Great! What feature do you like the most?';
} else {
q2Text.textContent = 'We are sorry to hear that. How can we improve?';
}
question2.appendChild(q2Text);
var input = document.createElement('textarea');
input.id = 'q2-input';
input.rows = 5;
input.style.width = '100%';
input.style.padding = '10px';
input.style.borderRadius = '5px';
input.style.border = '1px solid #ccc';
input.maxLength = '100';
question2.appendChild(input);
var charCount = document.createElement('div');
charCount.id = 'char-count';
charCount.style.textAlign = 'right';
charCount.style.fontSize = '12px';
charCount.style.color = '#999';
charCount.textContent = '0 / 100';
question2.appendChild(charCount);
input.addEventListener('input', function() {
var currentLength = input.value.length;
charCount.textContent = currentLength + ' / 100';
});
}
function filterInput(input) {
// Regular expression for email
var emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
// Replace emails with [email]
input = input.replace(emailPattern, '[email]');
// Regular expression for phone numbers
var phonePattern = /\b\d{8,}\b/g;
// Replace phone numbers with [phone]
input = input.replace(phonePattern, '[phone]');
return input;
}
function handleSubmit() {
var q1Answer = document.querySelector('input[name="q1"]:checked').value;
var q2Answer = document.getElementById('q2-input').value;
// Filter the q2 answer for emails and phone numbers
q2Answer = filterInput(q2Answer);
window.dataLayer.push({
event: 'question_2_answered',
question_2_answered: q2Answer
});
window.dataLayer.push({
event: 'survey_completed',
survey_completed: 'true'
});
showThankYouMessage();
}
function showThankYouMessage() {
container.innerHTML = '';
var thankYou = document.createElement('p');
thankYou.textContent = 'Thank you for your feedback!';
thankYou.style.fontWeight = 'bold';
thankYou.style.textAlign = 'center';
container.appendChild(thankYou);
setTimeout(function() {
container.style.right = '-320px';
localStorage.setItem('slideInLastClosed', new Date().getTime());
}, 3000);
}
setTimeout(function() {
container.style.right = '0';
}, 1000);
})();
</script>
10-07-’24:
1. Script update: Emails and phone numbers are filtered and will no longer show in GA4 due to privacy reasons. Since we can’t delete data from GA4 is very important that this personal data doesn’t end up in GA4.
08-07-’24:
1. Documentation: Added documentation on how to view survey results.
02-07-’24:
1. Tool: Started working on the builder (V1 soon will be ready).
2. Script update: Enhanced dataLayer data to events GTM documentation for GA4 analysis.
3. Tool: Creating Minified version of the script.
01-07-’24:
1. Question 2: Added character count to second question.
2. Question 2: Changed ‘Submit’ button to ‘Submit Feedback’.
3. Question 2: Changed the width of the submit button to 100%.
4. Increased the size of the close element from 20px to 24px.
28-06-’24:
1. Added z-index to the SIS is always before the content of the website.
The data that’s currently collected with this script:
Data collected | Description | Event Triggered | DataLayer Variables |
---|---|---|---|
Question 1 answer | User’s response to “Do you enjoy using our product?” | question_1_answered | event: 'question_1_answered' <br>question_1_answered: 'yes' or 'no' |
Question 2 answer | User’s response to the follow-up question based on their answer to Question 1 | question_2_answered | event: 'question_2_answered' <br>question_2_answered: User's text input |
Survey Completed | Indicates the survey has been completed after submitting the answer to Question 2 | survey_completed | event: 'survey_completed' <br>survey_completed: 'true' |
Slide-In Closed | Indicates the slide-in was closed by the user | slide_in_closed | event: 'slide_in_closed' <br>slide_in_closed: 'true' |
Last Closed Timestamp | The timestamp of when the slide-in was last closed, stored in localStorage | No event triggered directly | Stored in localStorage as slideInLastClosed |
As mentioned before, you’ll need admin access to do this part. We’re going to set up Custom dimensions to access the data in the future.
Dimension 1 – Question 1 answer
Dimension 2 – Question 2 answer
Dimension 3 – Survey completed
Dimension 4 – Slide-in closed
This data might take up to 48 hours to shown, I know … frustrating! That’s how GA4 works.
To do this, you’ll need publish access. It’s smart to do this in a new workspace in GTM and merge it once you’re done.
For each variable:
DLV - question_1_answered
)Variable name | Data Layer Variable name |
---|---|
dlv – SIS – Question 1 | question_1_answered |
dlv – SIS – Question 2 | question_2_answered |
dlv – SIS – Survey completed | survey_completed |
dlv – SIS – Slide in closed | slide_in_closed |
For each trigger:
Trigger - Question 1 answered
)Trigger name | Event name |
---|---|
Trigger – Question 1 answered | question_1_answered |
Trigger – Question 2 answered | question_2_answered |
Trigger – Survey completed | survey_completed |
Trigger – Slide-in closed | slide_in_closed |
For each tag:
question_1_answered
){{DL - question_1_answered}}
)Tag name | Event name | Event parameter name | Data layer variable |
---|---|---|---|
GA4 Event – SIS – Question 1 answered | question_1_answered | question_1_answered | dlv – SIS – Question 1 |
GA4 Event – SIS – Question 2 answered | question_2_answered | question_2_answered | dlv – SIS – Question 2 |
GA4 Event – SIS – Survey completed | survey_completed | survey_completed | dlv – SIS – Survey completed |
GA4 Event – SIS – Slide-In Closed | slide_in_closed | slide_in_closed | dlv – SIS – Slide in closed |
To analyse all the data correctly in GA4 you need to so some additional settings.
For the “GA4 Event – SIS – Question 2 answered,” you need to include the Event Parameters from “GA4 Event – SIS – Question 1 answered.” This allows you to filter the data in GA4 based on the answer to question one.
For the “GA4 Event – SIS – Survey completed,” you need to add the Event Parameters from both “GA4 Event – SIS – Question 1 answered” and “GA4 Event – SIS – Question 2 answered.”
Make sure to adjust the text to your wishes. You can search (CMD + F or CTRL + F) and replace these texts:
question_1_answered
, question_2_answered
, survey_completed
, slide_in_closed
) is firing as expectedquestion_1_answered
, question_2_answered
, survey_completed
, slide_in_closed
) appear in the DebugViewGTM firing events:
GA4 debugview:
I’ll be recording videos of this process in the future, but here’s a quick summary:
The easiest way to view the survey results:
Optional:
This filter will show all responses where the first question equals “yes.” You can also reverse this to show where the answers equal “no.”
Although we don’t collect personal data with the slide-in, GA4 does anyway. Since this tool works by sending data to GA4, it has to be behind the cookie bar. Make sure the slide-in only works if GA4 data collection is accepted!
I don’t collect any data! It all works with your GTM/GA4 account without any interference.
For sure I’ve got to find a way to optimize the GTM process without having so many triggers/tags. I hope you can now collect some additional user feedback!
Doing:
Next steps:
Future:
Working remotely from Groningen, the Netherlands. Get in touch and let’s schedule a meeting, no strings attached.
Get in touch