I'm using GTM to track submissions to an embedded Mailchimp form. Relevant post here: Tracking submissions on MailChimp embedded form
Per the original post answer, I am able to use this code to track form submissions.
$('form#mc-embedded-subscribe-form').submit(function(e) {
dataLayer.push({'event':'formSubmit'});
return true;
});
But right now, all clicks of the submit button are being tracked as form submissions, even if the form is not submitted. The answer included a tip to add e.preventDefault(); to prevent false form submissions from being tracked. Could someone tell me where I need to add preventDefault(), or if there's another method, how I can prevent false form submissions from being tracked.
I have tried inserting preventDefault() a number of places in the code, and have not gotten the desired result.
<!-- Begin Mailchimp Signup Form -->
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">
<style type="text/css">
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
/* Add your own Mailchimp form style overrides in your site stylesheet or in this style block.
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
</style>
<div id="mc_embed_signup">
<form action="https://..." method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
<div id="mc_embed_signup_scroll">
<h2>Subscribe</h2>
<div class="indicates-required"><span class="asterisk">*</span> indicates required</div>
<div class="mc-field-group">
<label for="mce-EMAIL">Email Address <span class="asterisk">*</span>
</label>
<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
</div>
<div class="mc-field-group">
<label for="mce-FNAME">First Name </label>
<input type="text" value="" name="FNAME" class="" id="mce-FNAME">
</div>
<div class="mc-field-group">
<label for="mce-LNAME">Last Name </label>
<input type="text" value="" name="LNAME" class="" id="mce-LNAME">
</div>
<div id="mce-responses" class="clear">
<div class="response" id="mce-error-response" style="display:none"></div>
<div class="response" id="mce-success-response" style="display:none"></div>
</div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_c46d540e26068777472a049e9_3aa4dd9218" tabindex="-1" value=""></div>
<div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
</div>
</form>
</div>
<script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script><script type='text/javascript'>(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';fnames[1]='FNAME';ftypes[1]='text';fnames[2]='LNAME';ftypes[2]='text';...fnames[13]='PAGEURL';ftypes[13]='text';}(jQuery));var $mcj = jQuery.noConflict(true);$('form#mc-embedded-subscribe-form').submit(function(e){dataLayer.push({'event':'formSubmit'});return true;});</script>
<!--End mc_embed_signup-->
The e.preventDefault() function only stops the form from submitting, it does nothing to check for validation and without having anything after it, nothing will happen.
The e.preventDefault() solution mentioned helps you to stop the trigger from being run, and then allows you to validate the form inputs before you actually trigger the event in Google Tag Manager.
The validation of inputs depend on what inputs are required in your form, so this will have to vary based on each form you create. But you could use a generic function that checks all inputs with class 'required', like so:
// Get all required inputs
var requiredFields = document.querySelectorAll('input.required');
// Create function to validate inputs
function validateInputs(callback){
// Set formstatus to valid
var formOkay = true;
// Check each field
requiredFields.forEach(i => {
// Check error state
if ((i.value == "") || ((i.type === 'checkbox') && (i.checked == false))) {
// Form was invalid
console.log('Form was invalid');
formOkay = false;
}
})
// Return formStatus;
callback(formOK ? true : false);
}
Now you can use this function to validate the form, before you send the Google Tag Manager trigger, like so:
document.getElementById('mc-embedded-subscribe-form').addEventListener('submit', e => {
e.preventDefault();
validateInputs( res => {
// check res
if (res == true){
dataLayer.push({'event':'formSubmit'});
}
})
})
Note:
This solution will check that the form inputs are valid, and not if the user was actually subscribed to the list. The best approach would be to subscribe the user, using ajax and then trigger the GTM-tag if the callback returns with status success.
I used a mutation observer to listen for changes to the div with the success message, which then could push an event to the dataLayer
// The element with success message
const successElement = document.getElementById('mce-success-response');
if(successElement){
const mutationConfig = { attributes: true };
const callback = function(mutationsList, observer) {
for(const mutation of mutationsList) {
if (mutation.type === 'attributes'
&& mutation.attributeName == 'style'
&& successElement.style.display === '') {
window.dataLayer.push({
"event" : "my-super-hot-lead"
})
}
}
};
const observer = new MutationObserver(callback);
observer.observe(successElement, mutationConfig);
}
Related
I am using a Wordpress theme that unfortunately is duplicating the header HTML for desktop, mobile and tablet. As a result, a login form I have appears to be submitting multiple times even though "Login" is only clicked once.
Here is the HTML for the form:
<div id="user-login">
<div class="com_row">
<div class="com_panel_body">
<div id="error_message91" class="com_alert com_alert_danger" style="display: none;">
</div>
<form method="post" id="validation_form83">
<input type="hidden" name="login_form_flag" value="1">
<div class="login-username">
<label for="email" class="main_label">Email Address</label>
<input id="email68" type="email" name="email" required="required">
</div>
<div class="login-password">
<label for="password" class="main_label">Password:</label>
<input id="password82" type="password" name="password" required="required">
</div>
<ul class="login-links" style="margin-top:-30px"><li>Forgot Password?</li></ul>
<div class="login-submit" style="margin-top:-20px">
<input type="submit" value="Login"></div>
<div style="padding-top:20px"><a class="button green small borderd-bot" href="/client_account">Register</a></div>
</form>
</div>
</div>
</div>
Here is the relevant JS:
$("[id^='validation_form']").each(function(i) {
//necessary because there are 3 form duplicates on the page, so this button works on all
jQuery(document).on("submit", this, SubmitValidationForm);
});
function($) {
SubmitValidationForm = function (event) {
event.preventDefault();
var formk = "#"+event.target.id;
var k = $(formk).serialize();
k += "&action=wcap_requests&what=validate_login";
jQuery("input[type=email]",formk).prop("disabled", true);
jQuery("input[type=password]",formk).prop("disabled", true);
jQuery("input[type=submit]",formk).prop("disabled", true).val(WCAP_Working_text);
var childf = $(formk).closest('div','.com_alert').children( ".com_alert");
$(childf).hide();
var login_form_flag = jQuery("input[name=login_form_flag]",formk).val();
jQuery.post(wcap_ajaxurl, k, function (data) {
data = JSON.parse(data);
console.log(data);
if (data.status === "OK") {
//== if client login through wcap login form
if (login_form_flag === '1'){
window.location.href = client_area_url;
}
else {
if (redirect_login !== "0") {
window.location.href = redirect_login;
} else {
window.location.reload();
}
}
}
else {
jQuery("input[type=email]",formk).prop("disabled", false);
jQuery("input[type=password]",formk).prop("disabled", false);
jQuery("input[type=submit]",formk).prop("disabled", false).val('Login');
$(childf).html(data.message).show();
}
});
};
};
The problem is because there are 3 duplicate forms on the page HTML (with only 1 visible to the user), the SubmitValidationForm function is called 3 times every time. The issue is pronounced when there is a valid login submitted, but the error box still appears saying invalid email after a few seconds (even though the login is actually correct and the user gets automatically redirected properly to the client area ). This error seems caused by the fact the SubmitValidationForm function is called 2 subsequent times after the first 'valid' submission which makes it think it's invalid, when it's not... the interesting thing is it doesn't seem caused by the other duplicate forms in the HTML, as the form ID attribute that I display in browser console shows only the 'valid' form being submitted (albeit multiple times -- perhaps because of the jquery.on() for each function).
Any ideas how to fix?
Thanks!
I figured out the issue. If anyone else is looking at this in future the issue was with respect to the 'on' function, it was referencing the 'document' before instead of 'this'. So it should be changed to:
$("[id^='validation_form']").each(function(i) {
jQuery(this).on("submit", this, SubmitValidationForm);
});
I was able to create a script to validate my Bootstrap 4 form but somehow the error message is REPLACING the input field. Is there an elegant way to validate a BS4 with Vanilla JS or I should just go down the road of using Bootstrap validation? What's the best practice in the industry? It's my first time dealing with form validation.
Here's the fiddle:
https://jsfiddle.net/wunrsjdy/
HTML:
<div class="container">
<div id="form">
<h1 class="page-title">Quer Ser Nosso Cliente? Preencha o QuestionĂ¡rio Abaixo</h1>
<form id="form-user" action ="#" method="POST">
<div class="form-group" id='errorTeste'>
<label for="name">Empresa</label>
<input type="text" class="form-control" id="name" name= "name" placeholder="">
</div>
<button type="submit" class="btn btn-primary" id="button-send">Enviar</button>
</form>
</div>
JS
const name = document.getElementById('name')
const form = document.getElementById('form-user')
const errorElement = document.getElementById('errorTeste')
form.addEventListener('submit', (e) => {
let messages = []
if (name.value === '' || name.value == null) {
messages.push('Preencha o nome')
}
if (messages.length > 0) {
e.preventDefault()
errorElement.innerText = messages.join(', ')
}
})
In your fiddle, you are changing the innerText attribute of errorElement. But errorElement is also (equal to) your .form-group element:
const errorElement = document.getElementById('errorTeste')
// A little further down your html...
<div class="form-group" id='errorTeste'>
I believe that this causes your input and label elements in .form-group to be replaced by the inner text.
I feel you are on the right track, though. Try showing another element that contains a styled error/warning message when a user input is invalid using the element's style.display attribute. Maybe you tried giving the #errorTeste id to a separate error message element, but assigned it to the .form-group element by accident? Just a hunch.
Here's a decent example of how you could make it look:
I am trying to save an HTML field (for later use in a form) from a JS script.
This is the code:
Form
<form class="new_client" id="new_client" action="/clients" accept-charset="UTF-8" method="post">
<div class="form-group">
<input class="form-input" type="hidden" name="client[city]" id="client_city">
</div>
<div class="form-group">
<input class="form-input" type="hidden" name="client[address]" id="client_address">
</div>
<div id="locationField">
<input autocomplete="off" class="autocomplete" placeholder="Enter your address" type="text">
</div>
<div class="text-center">
<button class="btn button-general ">Save</button>
</div>
</form>
And the javascript:
function configureGeoAutocomplete(context) {
if( context === undefined ) {
context = $('body');
}
var element = context.find('.autocomplete')
//It doesn't really matter what this line does, it's from Google Places API
var autocomplete = new google.maps.places.Autocomplete(
element[0], {types: ['geocode']});
autocomplete.addListener('place_changed', fillInAddress)
}
function fillInAddress() {
var client_address = autocomplete.getPlace().formatted_address;
document.getElementById("client_address").value = client_address;
}
The javascript is queried when loading the modal in which the form is
jQuery(function() {
$('div.modal').on('loaded.bs.modal', function(e) {
configureGeoAutocomplete(context);
}
}
I wanna save that client_address to the text field so when the form is submitted I can have that information.
Sounds like an excellent candidate for cookies if you are allowed to use them: http://www.w3schools.com/js/js_cookies.asp. Another idea is to pass it along in a Query String. It isn't PII or anything like that. Try an event code on that input. I do not like to hit "enter"!
onkeypress="if (event.keycode==13) { // do something }"
Handle the form submit event:
$(function() {
$("#new_client").submit(function(e) {
e.preventDefault();
// This is your value stored in the field.
$("#client_address").val();
});
})
Apparently, for some reason, if I searched the element by ID it was not saving the information on the field. If I istead search by class:
function fillInAddress() {
var place = autocomplete.getPlace();
$(".client-address").val(place.formatted_address);
}
It works as intended.
This is my first time using AngularJS, and the form validation is making me question my sanity. You would think this would be the easy part, but no matter how many ways I've tried Googling, the only thing that works is if I set a flag inside my controller's submit if the form is invalid to set the error class. I've looked at similar problems here, but none of them helped, so please do not simply dismiss this as a potential duplicate. Everything else has been a fail.
In the example mark up below I have reduced my form down to just one element. Here is what I have observed:
Using only $error.required does work. The ng-class { 'has-error' :registerForm.firstName.$error.required} does outline the text box with the bootstrap has-ertror class, but this is on form load, which I do not want.
The <p> element with the error message will exhibit the same behavior, so I know that the message exists and is not malfored. It will also display if I only use $error.required. But as soon as I add && registerForm.$submitted ( or $isdirty or !notpristine ) the message will not display on form submit. There are no errors (have developers tools open in chrome) and will post to the web API with no problem and return ok 200 or 400 if I send bad params.
I can write validation code inside my controller, checking if the field has a value and setting a flag on $scope such as $scope.firstNameIsRequired and that will work fine setting ng-show="$scope.firstNameIsRequired", but that will remove testability.
So the problem definitely has to be with how I am adding this in the markup. But after a weekend spent googling I am at my wits end. The only other thing different is that I am using a span on a click element to submit the form instead of an input = submit, but the registerForm.$valid function is setting the correct value. Do I somehow need to trigger the form validation in that ng-click directive?
I am using angular.js v 1.4.8.
I do have angular ui which has it's own validate, but that shouldn't interfere with the basic validation.
Here is the simplified markup:
<form name="registerForm" class="form-group form-group-sm"
ng-controller="userAccountController" novalidate>
<div class="form-group"
ng-class="{ 'has-error' : registerForm.firstName.$error.required }">
<div><label>First Name</label> </div>
<input type="text" class="form-control" id="firstName" name="firstName" value=""
ng-model="firstName" placeholder="First Name" maxlength="100" required=""/>
<p ng-show="registerForm.firstName.$error.required && registerForm.$submitted"
class="alert alert-danger">First Name is required</p>
</div>
<div>
<span class="btn btn-default"
ng-click="submit(registerForm.$valid)">Register</span>
</div>
My controller code is
angular.module( "Application" ).controller( "userAccountController", [
"$scope", "userAccountService", function ( $scope, userAccountService)
{
$scope.hasErrors = false;
$scope.errorMessages = "";
$scope.emailExists = true;
$scope.clearErrors = function (){
$scope.hasErrors = false;
}
$scope.onSuccess = function ( response ) {
alert( "succeeded" );
}
$scope.submit = function (isValid) {
if ($scope.registerForm.$invalid)
return;
alert("isvalid");
$scope.clearErrors();
var userProfile = $scope.createUser();
userAccountService.registerUser(userProfile, $scope.onSuccess, $scope.onError);
}
$scope.createUser = function () {
return {
FirstName: $scope.firstName, LastName: $scope.lastName, Email: $scope.email,
Password: $scope.password, SendAlerts: $scope.sendAlerts
};
};
}
]);
Any help will be appreciated. I probably just need a second set of eyes here because I have been dealing with this on and off since late Friday.
in angular you want use the element.$valid to check wheter an model is valid or not - and you use element.$error.{type} to check for a specific validation error.
Keep in mind that the form.$submitted will only be set if the form is actually submitted - and if it has validationerrors it will not be submitted (and thus that flag is still false)
If you want to show errors only on submit you could use a button with type="submit" and bind to ng-click event - and use that to set a flag that the form has been validated. And handling the submit if the form is valid.
A short example with 2 textboxes, having required and minlength validation:
angular.module("myApp", [])
.controller("myFormController", function($scope) {
$scope.isValidated = false;
$scope.submit = function(myForm) {
$scope.isValidated = true;
if(myForm.$valid) {
console.log("SUCCESS!!");
}
};
});
.form-group {
margin: 10px;
padding: 10px;
}
.form-group.has-error {
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.js"></script>
<div ng-app="myApp" ng-controller="myFormController">
<form name="myForm">
<div class="form-group" ng-class="{'has-error': myForm.name.$invalid && isValidated}">
<span>Name:</span>
<input type="text" name="name" minlength="5" ng-model="name" required />
<span ng-if="myForm.name.$error.required && isValidated">Name is required</span>
<span ng-if="myForm.name.$error.minlength && isValidated">Length must be atleast 5 characters</span>
</div>
<div class="form-group" ng-class="{'has-error': myForm.email.$invalid && isValidated}">
<span>Email:</span>
<input type="text" name="email" minlength="5" ng-model="email" required />
<span ng-if="myForm.email.$error.required && isValidated">Email is required</span>
<span ng-if="myForm.email.$error.minlength && isValidated">Length must be atleast 5 characters</span>
</div>
<button type="submit" ng-click="submit(myForm)">Submit</button>
</form>
</div>
I'm having a problem with a simple html login page I made, where when I submit the login form with invalid credentials the form still submits, even though my validation method is executing the "return false" command. I know that this question has been asked a multitude of times here, but none of the answers to those questions have helped me.
My html form is as follows:
<form onSubmit="return validateForm();" method="get" action="TestPage.html">
<div id="centralPoint" class="frame">
<select id="centralPointSelect" data-inline="true" data-mini="true" data-native-menu="false" name="centralPoint"></select>
</div>
<div id="credentialsFrame" class="frame">
<input id="userField" type="text" name="Username" />
<input id="passField" type="password" name="Password" />
</div>
<div id="errorMsg"></div>
<input id="loginBtn" type="submit" value="Login" />
<div id="rememberMe" class="frame">
<input type="checkbox" id="checkbox-1" class="custom" data-mini="true" name="rememberMe" />
<label for="checkbox-1">Keep me signed in</label>
</div>
<div id="forgotPassFrame">
<input id="forgotPass" type="button" data-mini="true" value="Forgot password?" />
</div>
</form>
And here is my javascript method. Note that even if the only line of code in here is "return false;" the form still submits. I've also checked in firebug to make sure that the method is actually being called and that it is indeed returning false, and it all checks out.
function validateForm()
{
var usernameTxt = $("#userField").attr("value");
var passwordTxt = $("#passField").attr("value");
if (usernameTxt == "" || passwordTxt == "" || (usernameTxt == userLbl && passwordTxt == passLbl))
{
$("#errorMsg").html("Please enter a username and password.");
return false;
}
}
Is there something really obvious that I'm missing? I'm not binding the onsubmit event in any way other than what you can see in the html form tag, nor am I assigning any click handler to the submit button.
It might be relevant that I'm using JQuery mobile, is it possible that this is doing something with my form?
If you want to handle form submission on your own, you will need to add the data-ajax="false" attribute to the <form> tag so jQuery Mobile leaves it alone.
To prevent form submissions from being automatically handled with
Ajax, add the data-ajax="false" attribute to the form element. You can
also turn off Ajax form handling completely via the ajaxEnabled global
config option.
Source: http://jquerymobile.com/demos/1.1.0/docs/forms/forms-sample.html
Here is a demo of your form but with the above attribute added: http://jsfiddle.net/XtMw9/
Doing this means that you will have to manually submit your form:
function validateForm()
{
var usernameTxt = $("#userField").val();
var passwordTxt = $("#passField").val();//notice the use of .val() instead of .attr()
if (usernameTxt == "" || passwordTxt == "" || (usernameTxt == userLbl && passwordTxt == passLbl))
{
$("#errorMsg").html("Please enter a username and password.");
return false;
}
var $form = $('form');
$.ajax({
url : $form.attr('action'),
type : $form.attr('method'),
data : $form.serialize(),
success : function (response) { ... },
error : function (a, b, c) { console.log(b); }
});
}
Explanation
This works because by default jQuery Mobile will attempt to submit any form via AJAX. Using the data-ajax="false" attribute we can tell jQuery Mobile to leave a specific form alone so we can submit it on our own.
In validateForm() function, you've used two undefined variables(userLbl and passLbl).
Define value for the variables and check.