Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Alex Skrypnyk 6132 posts 23951 karma points MVP 7x admin c-trib
    Sep 20, 2016 @ 17:33
    Alex Skrypnyk
    0

    Ajax Umbraco Forms

    Hi Community,

    What is the best way of doing Ajax Umbraco Forms?

    Is it possible out of the box in latest version?

    Thanks,

    Alex

  • Amir Khan 1282 posts 2739 karma points
    Sep 20, 2016 @ 19:32
    Amir Khan
    1

    See the method posted by Thomas here, it works with some simple updates to the main view for the form: https://our.umbraco.org/forum/umbraco-pro/contour/60788-Umbraco-Forms-and-Ajax#comment-233528

  • Alex Skrypnyk 6132 posts 23951 karma points MVP 7x admin c-trib
    Sep 20, 2016 @ 20:10
    Alex Skrypnyk
    0

    Thanks, Amir, nice solution, will try.

    Maybe something easier?

    Thanks

  • Alex Skrypnyk 6132 posts 23951 karma points MVP 7x admin c-trib
    Sep 22, 2016 @ 20:10
    Alex Skrypnyk
    101

    Solved! This code works for me:

    <script type="text/javascript">
            $(document)
                .ready(function() {
                    //Intercept Submit button in order to make ajax call instead of a postback
                    $('#contour_form_@FORMID').preventDoubleSubmission();
                });
    
    // jQuery plugin to prevent double submission of forms
        jQuery.fn.preventDoubleSubmission = function () {
            $(this).on('submit', function (e) {
                e.preventDefault();
    
                var $form = $(this);
    
                if ($form.data('submitted') === true) {
                    // Previously submitted - don't submit again
                } else {
                    if ($form.valid()) {
                        // Mark it so that the next submit can be ignored
                        $form.data('submitted', true);
    
                        // Make ajax call form submission
                        $.ajax({
                            url: $form.attr('action'),
                            type: 'POST',
                            cache: false,
                            data: $form.serialize(),
                            success: function (result) {
                                console.log(result);
    
                                var thankYouMessage = $(result).find('.contourMessageOnSubmit');
                                $('#contour_form_@FORMID').html(thankYouMessage);
    
                            }
                        });
                    }
                }
            });
    
            // Keep chainability
            return this;
        };
        </script>
    

    Thanks,

    Alex

  • Danny Summers 22 posts 82 karma points
    Dec 18, 2018 @ 07:46
    Danny Summers
    3

    2018 now and this solution came in handy. I reworked it slightly to suit the change of class names to "umbraco-forms" and to make it more generic (so this would go in a generic JS file).

    $(document).ready(function() {
        //Intercept Submit button in order to make ajax call instead of a postback
        $('.umbraco-forms-form form').preventDoubleSubmission();
    });
    
    // jQuery plugin to prevent double submission of forms
        jQuery.fn.preventDoubleSubmission = function () {
            $(this).on('submit', function (e) {
                e.preventDefault();
    
                var $form = $(this);
    
                if ($form.data('submitted') === true) {
                    // Previously submitted - don't submit again
                } else {
                    if ($form.valid()) {
                        // Mark it so that the next submit can be ignored
                        $form.data('submitted', true);
    
                        // Make ajax call form submission
                        $.ajax({
                            url: $form.attr('action'),
                            type: 'POST',
                            cache: false,
                            data: $form.serialize(),
                            success: function (result) {
                                var thankYouMessage = $(result).find('.umbraco-forms-submitmessage').first();
                                $form.html(thankYouMessage);
                            }
                        });
                    }
                }
            });
    
            // Keep chainability
            return this;
        };
    
  • Alex Skrypnyk 6132 posts 23951 karma points MVP 7x admin c-trib
    Dec 18, 2018 @ 10:24
    Alex Skrypnyk
    0

    Hi Danny

    Thank you so much for sharing!!!

    Alex

  • Amir Khan 1282 posts 2739 karma points
    Dec 18, 2018 @ 14:31
    Amir Khan
    0

    Do you guys think it would be useful to turn this into a package or Forms theme? I remember the first time implementing this for contour years ago and it was a challenge to even find where to put these scripts.

    Would be nice if it were more plug and play and could be updated in one place as the markup / scripts for Forms evolve.

    I'd be happy to do it if so.

    -Amir

  • Danny Summers 22 posts 82 karma points
    Dec 19, 2018 @ 02:29
    Danny Summers
    0

    I don't think a package would add much value with my tweaked solution above, as it's not a C# solution, but simple JQuery. This is generic and would just be thrown into a JS file (either in the head of end of should work).

    That earlier Contour solution (where it included some Razor), would have needed to be included in a specific razor file to work.

  • Stefano 61 posts 313 karma points c-trib
    Feb 06, 2019 @ 09:37
    Stefano
    0

    I don't think this will work with multi-steps forms and be compatible with thank you pages redirection tho!

  • Alex Skrypnyk 6132 posts 23951 karma points MVP 7x admin c-trib
    Feb 06, 2019 @ 10:28
    Alex Skrypnyk
    0

    Hi Stefano, you are right. It's not a perfect solution.

  • Stefano 61 posts 313 karma points c-trib
    Feb 06, 2019 @ 10:41
    Stefano
    1

    Spent a few hours on this and here are my findings:

    • https://github.com/jeppe-smith/umbraco-ajax-form seems a good place to start if you want ajax. I didn't test it but the code seems to support multi-steps forms
    • ajax protocol can't detect redirects, the browser makes it invisible for it. This can be done with a new API called fetch which has around 85% users support according to canisue
    • Seems a bit of a can of worms. I don't think you'll be able to remove the "redirect to page" functionality from forms itself, so fundamentally you'd be breaking a core functionality of Umbraco Forms. Bad karma.
    • What I thought I could do in my case was to rely on something on the page so that I know that the response is a different URL (e.g. I've been redirected). Canonical meta tags are a good candidate. I could just do
      • Get result html
      • Get canonical meta value
      • Is it the same url ? (handle fragments, query strings and protocols here)
      • If different it was a redirect, redirect to that page.
      • This sounds a bit hacky and would avoid if not necessary
  • Stefano 61 posts 313 karma points c-trib
    Feb 06, 2019 @ 11:09
    Stefano
    1

    I've just tested this solution which as un-sexy as it is is dead simple and works with all Umbraco Forms functionality

    USE CODE FROM MY NEXT ANSWER INSTEAD!

    // Find a submit message that is not wrapped in a form (the message is always present even when not visible
    // as the form has just been loaded.
    let message = $('.umbraco-forms-submitmessage:not(form .umbraco-forms-submitmessage)').first();
    if(message)
    {
      // Scroll page to the submit message. Here 20 is just to add some margin, but you could need to add
      // the height of your sticky navbar too.
      let targetOffset = message.offset().top - 20;
      $('html,body').animate({scrollTop: targetOffset});
    }
    

    This however doesn't scroll down to the 2nd+ step of forms, so I'll have a look at that now

  • Alex Skrypnyk 6132 posts 23951 karma points MVP 7x admin c-trib
    Feb 06, 2019 @ 11:12
    Alex Skrypnyk
    0

    awesome finding Stefano, didn't try umbraco-ajax-form but it looks interesting

  • Stefano 61 posts 313 karma points c-trib
    Feb 06, 2019 @ 15:30
    Stefano
    1

    I'll one up myself by supporting multi pages forms, and I've opened a ticket in regards to previous pages hidden field not being populated:

    $(document).ready(function () {
        function scrollToFormIfInteractedWith() {
          // Find a target to scroll to if the user is interacting with forms. This happens in different ways:
          // - Find a submit message that is not wrapped in a form (the message is always present even when not visible
          // as the form has just been loaded.
          // - If the message wasn't found then try to target a form containing a hidden field that indicates that the form's
          // step/page is not the first(0) one.
          // - If that wasn't found either then try to find a form that has PreviousClicked = true where the user just
          // navigated back. This is CURRENTLY NOT WORKING possibly because of an Umbraco Forms issue for which I've raise
          // a github issue here https://github.com/umbraco/Umbraco-CMS/issues/4443.
          let target = $('.umbraco-forms-submitmessage:not(form .umbraco-forms-submitmessage)')[0]
              || $('input[type="hidden"][name="FormStep"]:not([value="0"]),'
                  + 'input[type="hidden"][name="PreviousClicked"][value="true"]').parent('form')[0];
    
          // If we have found a target to scroll to then smooth scroll there.
          if (target) {
            // Scroll page to the target. Here 20 is just to add some margin, but you could need to add
            // the height of your sticky navbar too.
            let targetOffset = $(target).offset().top - 20;
            $('html,body').animate({scrollTop: targetOffset});
          }
        }
    
        scrollToFormIfInteractedWith();
      });
    
  • Danny Summers 22 posts 82 karma points
    Feb 08, 2019 @ 00:19
    Danny Summers
    0

    Just to expand on the AJAX approach, which still doesn't address the issue with multi step forms & redirections, but thought I should document my tweaks here as this will come up for others also.

    Our original AJAX solution doesn't work with posting files to the server and with handling Recaptcha errors. A long way from perfect, but my basic code tweak is below:

    $(document).ready(function() {
        //Intercept Submit button in order to make ajax call instead of a postback
        $('.umbraco-forms-form form').preventDoubleSubmission();
    });
    
    // jQuery plugin to prevent double submission of forms
    jQuery.fn.preventDoubleSubmission = function () {
        $(this).on('submit', function (e) {
            e.preventDefault();
    
            var $form = $(this);
    
        if ($form.data('submitted') === true) {
            // Previously submitted - don't submit again
        } else {
            if ($form.valid()) {
                // Mark it so that the next submit can be ignored
                $form.data('submitted', true);
    
                // Make ajax call form submission
                $.ajax({
                    url: $form.attr('action'),
                    type: 'POST',
                    cache: false,
                    data: new FormData(this),
                    processData: false,
                    contentType: false,
                    success: function (result) {
                        var thankYouMessage = $(result).find('.umbraco-forms-submitmessage').first();
                        //Handles edge case where Recaptcha wasn't checked
    
                        if (thankYouMessage.length == 0) {
                            $(result).find('.field-validation-error').each(function (i, v) {
                                window.alert($(v).text());
                            });
    
                            $form.data('submitted', false);
                        }
                        else {
                            $form.html(thankYouMessage);
                        }
                    }
                });
            }
        }
    });
    
    // Keep chainability
    return this;
    };
    
  • Matthew 13 posts 88 karma points
    Dec 09, 2020 @ 23:18
    Matthew
    2

    Has anyone tried this solution with the latest version of forms? I'm trying this with 8.6 and the page still reloads on submit. The return message shows first and then the page reloads almost as if it's ignoring preventDefault? I've tried to figure this out all day and have had no lucky. Any suggestions?

  • lori ryan 239 posts 573 karma points
    May 06, 2021 @ 18:31
    lori ryan
    0

    any luck?

  • lori ryan 239 posts 573 karma points
    May 08, 2021 @ 07:50
    lori ryan
    0

    Hi Alex Just wondering do u get a solution that works for this? Am struggling to implement. When I implement it still jumps to top of page

  • Aaron 57 posts 405 karma points MVP c-trib
    May 20, 2021 @ 11:25
    Aaron
    1

    I got this working on a single stepped form with Umbraco forms 8.7 using the below jquery:

    <script type="text/javascript">
            $(document)
                .ready(function() {
                    //Intercept Submit button in order to make ajax call instead of a postback
                    $('#umbraco_form_@(Guid.Parse((string) Model.YOURFORMID).ToString("N")) form').preventDoubleSubmission();
                });
            // jQuery plugin to prevent double submission of forms
            jQuery.fn.preventDoubleSubmission = function () {
                $(this).on('submit', function (e) {
                    e.preventDefault();
    
                    var $form = $(this);
    
                    if ($form.data('submitted') === true) {
                        // Previously submitted - don't submit again
                    } else {
                        if ($form.valid()) {
                            // Mark it so that the next submit can be ignored
                            $form.data('submitted', true);
    
                            // Make ajax call form submission
                            $.ajax({
                                url: $form.attr('action'),
                                type: 'POST',
                                cache: false,
                                data: $form.serialize(),
                                success: function (result) {
                                    console.log(result);
    
                                    var thankYouMessage = $(result).find('.umbraco-forms-submitmessage');
                                    $('#umbraco_form_@(Guid.Parse((string) Model.YOURFORMID).ToString("N"))').html(thankYouMessage);
    
                                }
                            });
                        }
                    }
                });
    
                // Keep chainability
                return this;
            };
        </script>
    
  • Jesse Andrews 191 posts 716 karma points c-trib
    Sep 29, 2021 @ 22:48
    Jesse Andrews
    1

    I was able to get a multi step form to submit successfully via ajax with the following code.

    angular.module(module || 'app')
        .controller('UmbracoFormsController', UmbracoFormsController);
    
    UmbracoFormsController.$inject = ['$rootScope', '$scope', '$element', '$attrs', '$http', '$compile'];
    function UmbracoFormsController($rootScope, $scope, $element, $attrs, $http, $compile) {
        var ctrl = this;
        ctrl.$onInit = function () {
        };
    
        ctrl.goBack = () => {
            ctrl._goBack = true;
        }
    
        ctrl.submitForm = function ($event, isMultiStep) {
            $event.preventDefault();
    
            if (!ctrl.submitting) {
                ctrl.submitting = true;
                postForm(isMultiStep);
            }
        };
    
        function postForm(isMultiStep) {
            var valid = false;
            if (!ctrl._goBack) {
                $element.validate();
                valid = $element.valid();
                if (!valid) {
                    ctrl.submitting = false;
                }
            }
    
            if (ctrl._goBack || valid) {
                var data = {};
                var values = $element.serializeArray();
                values.forEach(function (item, index) {
                    if (data[item.name] === undefined) { // New
                        data[item.name] = item.value || '';
                    } else {                            // Existing
                        if (!data[item.name].push) {
                            data[item.name] = [data[item.name]];
                        }
                        data[item.name].push(item.value || '');
                    }
                });
                if (ctrl._goBack) {
                    data['__prev'] = $element[0].querySelector('[name="__prev"]').innerText;
                }
                $.post({
                    method: 'POST',
                    url: location.pathname,
                    data: data,
                    success: function (response) {
                        var html = document.createElement('html');
                        html.innerHTML = response;
                        var container = $element.parent();
                        var id = container.attr('id').replace('umbraco_form_', '');
                        var form = html.querySelector('#' + $element.parent().attr('id'));
                        if (!form) {
                            var popup = $('#form_popup_' + id);
                            popup.foundation('open');
                            var pageForms = Array.from(document.querySelectorAll('#umbraco_form_' + id + ' form'));
                            pageForms.forEach(pageForm => {
                                pageForm.reset();
                            });
                        }
                        else if (isMultiStep) {
                             $element.html(form.innerHTML);
                             $compile($element.contents())($scope);
                             $element.removeData("validator").removeData("unobtrusiveValidation");
                             $.validator.unobtrusive.parse($element);
                        }
                        $scope.$evalAsync(function () {
                            ctrl.submitted = true;
                            ctrl.submitting = false;
                        });
                    },
                    error: function () {
                        $scope.$evalAsync(function () {
                            ctrl.submitting = false;
                        });
                    }
    
                });
            }
            ctrl._goBack = false;
        }
    }
    

    My solution uses angularjs, but the same technique would work for any javascript framework. It involves the following.

    1. Determine if the "previous" button triggered the submission and if it did, add a value for the name "__prev" to the data that will be posted (the code does this by setting the "goBack" flag to true when that button is clicked and then checking that flag). This will send the form back to the previous step. If it's left out, the form moves on to the next step.
    2. Post the form data at the current url (same as what's done for single step forms).
    3. Find the form in the response html.
    4. If no form is found in the response html, the form has been fully submitted and a modal with a thank you message is displayed (I'm using foundation reveals for the message, so the foundation call is used to reveal it). Otherwise it moves on to step 5.
    5. Replace the content in the form with the form content from the response.
    6. Apply any scripts that need to be run against the newly injected content (this is what the $compile clause is doing in my code).
    7. Reinitialize the unobtrusive validation, as it won't automatically pick up on the new inputs (lines with removeData("validator") and $.validator.unobtrusive.parse($element)).
  • MB 113 posts 422 karma points
    Sep 30, 2021 @ 02:07
    MB
    1

    One point that might be worth knowing - if using reCAPTCHA-3 and AJAX, you may need to reload the reCAPTCHA-3 client in the Success callback of the Post, otherwise it will likely exit to a missing reCAPTCHA-3 client and throw an error along the lines of: "Error: No reCAPTCHA clients exist".

    I solved this for my use by adding a function to the reCAPTCHA-3 field (so I could easily init the keys, form-id, etc) and calling that from the $.Post Success callback, but I'm sure there are more elegant ways to handle it.

  • Jesse Andrews 191 posts 716 karma points c-trib
    Sep 30, 2021 @ 15:34
    Jesse Andrews
    0

    I need to test my integration with a recaptcha field, but you're probably right on that. Thanks for pointing it out.

  • Jesse Andrews 191 posts 716 karma points c-trib
    Oct 07, 2021 @ 22:19
    Jesse Andrews
    0

    @MB You were right. Out of the box, the recaptcha 3 implementation initializes after the "DOMContentLoaded" event fires. Since the recaptcha loads after this step in an ajax multi step form, I tweaked the script to check whether the dom has already initialized and initialize recaptcha if it has. I changed it from

    document.addEventListener('DOMContentLoaded', function () {
        // Disable the submit button for this form, until we actually have a key from Google reCAPTCHA
        hiddenField.form.querySelector('[type=submit]').setAttribute('disabled','disabled');
    
        window.grecaptcha.ready(function () {
            timerFunction();
        });
    });
    

    to

    function init() {
        hiddenField.form.querySelector('[type=submit]').setAttribute('disabled','disabled');
    
        window.grecaptcha.ready(function () {
            timerFunction();
        });
    }
    window.loadScript("https://www.google.com/recaptcha/api.js?render=@siteKey", function() {
        if (document.readyState !== 'loading') {
            init();
        }
        else {
            document.addEventListener('DOMContentLoaded', init);
        }
    });
    

    "loadScript" is just a helper method I created to load scripts via javascript and then run the optional callback function after that script has loaded. Note that the loadScript wrapper may not be required. For some reason

    Html.AddFormThemeScriptFile(null, "https://www.google.com/recaptcha/api.js?render=" + siteKey);
    

    didn't load the script in my build, so I loaded it with that utility method. Suspect it's because of all the changes I've made to the form views though.

Please Sign in or register to post replies

Write your reply to:

Draft