On March 26, 2019 we launched new MODX Forums. Please join us at the new MODX Community Forums.
Subscribe: RSS
  • Hi guys,

    I felt like sharing this because it took us a while to clean this up. Forms are not just forms any more. They have frontend validation, server validation, then after the form has submitted, certain things should happen, etc. etc.

    So here's a drop-in replacement chunk for render_invisible_html - call it something else, otherwise it will be overwritten when updating ReCaptchaV2.

    <script async defer type="text/javascript" src="https://www.google.com/recaptcha/api.js?hl=[[++cultureKey]]"></script>
    <div id='recaptcha' class="g-recaptcha"
         data-sitekey="[[+site_key]]"
         data-callback="onCompleted"
         data-size="invisible"></div>
    <script>
    var captureCompleted = false;
     window.onload = function () {
      jQuery('#[[+form_id]]').submit(function(event) {
        console.log('validation passed, here comes the captcha.');
        grecaptcha.execute();
    
        if ( !captureCompleted ) return false;
    
        // this is where you would put your AdWords conversion code or anything that you would like to be triggered after captcha completion.
        // you can of course also use the postHooks for FormIt, but if you trust your frontend validation and don't do anything super important here
        // this is the place to put your code.
    
      });
      onCompleted = function() {
        console.log('ReCaptchaV2 finished successfully.');
        captureCompleted = true;
      }
    };
    </script>
    


    Why we built this: Conversion rates of a clients AdWords campaign dropped dramatically after we added the "I am not a robot" captcha. Apparently, many people are just too <insert mean word here> to click the button. We had no explanation for this, but ever since we turned it off, everything worked out fine.

    Have fun using this and let me know if you have any improvements or anything that we might have missed.

    Andreas
    • Hey again,

      apparently there is a timing-issue for this which causes the code to (sometimes) prevent the form from sending and after that "unlock" the button. Meaning: you will need to click the submit-button twice. We missed this because people just clicked the button twice because they thought they just didn't click it correctly.

      Either way, I would be happy if someone would try this out with the recaptchav3 hook. For now, we fixed the behavior above by sending all of the information via an ajax call, which restricts it to javascript operation and avoids any posts and redirects to other pages. So the timing problems go away when you setup a resource as a target to send to and make FormIt use ajax to post.

      Just to clarify: The FormIt snippet is just the "receiver" for the action. So here's what you do to avoid this:

      #1: Create a resource as a FormIt Ajax Responder
      #2: Assign a template to it with the FormIt call (make sure it responds JSON, see documentation here https://docs.modx.com/extras/revo/formit and an example here: https://sepiariver.com/modx/modx-forms-via-ajax/)
      #3: Make the form post to that target and do the checking with the according libs inside the Javascript:

      NOTE: THIS MIGHT ALL NOT BE NECESSARY WITH V3!

      (function($){
        
        $.notifyDefaults({
          z_index: 100000,
          type: 'success',
          allow_dismiss: false,
          delay: 5000,
          newest_on_top: true,
          offset: {
          	x: 50,
            y: 100
          }
        });	
      
      	var submitting = false;
      	var captureCompleted = false;
      
        onError = function() {
      		console.log('captcha failed.');
      		if ( submitting ) {
      			$.notify({
      				title: "[[+errorTitle]]",
      				message: "[[+errorMsg]]",
      				icon: 'fa fa-warning'},{
      				type: "danger"
      			});      				
      		}
          submitting = false;
          var $this = $("#[[+formid]]"); // hard-code if you don't want to build that.
          $this.find(".form-group").removeClass("has-error").removeClass("has-success");
          $this.find('i.fa').removeClass('fa-cog fa-spin');
          $this.find('button').removeClass("disabled");
        };
      	
      	onCompleted = function() {
      		console.log('captcha completed.');
      		captureCompleted = true;
      		
      		if ( !captureCompleted ) {
      			console.log('captcha failed.');
      			submitting = false;
      			return false;
      		}
      		
      		var $this = $("#[[+formid]]"), formData = $("#[[+formid]]").serializeArray();
      		
      		/* reset posibble form errors the form when submitting */
      		$this.find(".form-group").removeClass("has-error").addClass("has-success");
      		$this.find("span").remove();
      		
      		$.ajax({
      			type: 'POST',
      			url: $this.attr('action'),
      			dataType: 'json', 
      			data: formData,
      			success: function(response) {
              			  
      			  if (typeof gtag_report_conversion === "function") {
      			    gtag_report_conversion();
      			  }
      
      				$this.find('i.fa').removeClass('fa-cog fa-spin');
      				$this.find('button').removeClass("disabled");
      
      				if (response.success === true) {						
      					$.notify({
      						message: response.message,
      						icon: 'fa fa-check'
      					});
      					
      					/* reset the form */
      					$($this)[0].reset();
      					$this.find(".form-group").removeClass("has-error").removeClass("has-success");
                $this.find(":input").removeClass("valid");
      				}
      				else {						
      					$.notify({
      						title: "[[+errorTitle]]",
      						message: "[[+errorMsg]]",
      						icon: 'fa fa-warning'},{
      						type: "danger"
      					});      				
      					
      					$.each( response.errors, function( index, value ) {
      						$("#" + index).parent().addClass("has-error").removeClass("has-success");
      						$('<span id="'+index+'-error" class="has-error help-block">'+value+'</span>').insertAfter("#"+index);
      					});
      					console.log("FormIt validation error");
      					submitting = false;
      				}
      			},
      			error: function(response) {
      				$this.find('i.fa').removeClass('fa-cog fa-spin');
      				$this.find('button').removeClass("disabled");					
      
      				$.notify({
      					title: "[[+errorTitle]]",
      					message: "[[+serverErrorMsg]]",
      					icon: 'fa fa-warning'},{
      					type: "danger"
      				});      				
      				
      				$this.find(".form-group").removeClass("has-error").removeClass("has-success");
      				console.log("FormIt Ajax Error");					
      				submitting = false;
      			}
      		});
      	};
      	
      	$(document).ready(function(){			
      		/* only if we have a contact form on page */
      		if($("#[[+formid]]").length>0) {
      			
      			/* validate the form - triggers jquery plugin */				
      			var validator = $("#[[+formid]]").validate({
      			  errorClass: "has-error help-block",
      				errorPlacement: function(error, element) {
      					error.insertAfter( element );
      				},
      				errorElement: "span",
      				highlight: function (element) {
      				  $(element).removeClass("valid");
      					$(element).parent().removeClass("has-success").addClass("has-error");
      				},
      				success: function (element) {
      				  $(element).addClass("valid");
      					$(element).parent().removeClass("has-error").addClass("has-success");
      				}
      			});
      			
      			/* real submit function */
      			$(document).on("submit", "#[[+formid]]", function (event) {
      				event.preventDefault();
      				var $this=$(this);
      				/* Do not trigger if the user already triggered the form */
      				if (submitting === true) return false;
      				submitting = true;
      				console.log("recaptcha initialized");
              $this.find('i.fa').addClass('fa-cog fa-spin');
      				$this.find('button').addClass("disabled");
      				/* Trigger google recaptcha. If this returns true either the callback "onCompleted" or "onError" will be executed */
      				grecaptcha.reset();
      				grecaptcha.execute();
      			});
      		}		
      	});									
      })(this.jQuery);
      


      Don't forget to add all the libs (recaptcha, gtm, etc. etc.). In our case there's also Bootstrap notify for the promises and stuff, you will need to add those or fix the code above to match your scenario.

      Andreas