Showing posts with label JQuery. Show all posts
Showing posts with label JQuery. Show all posts

Thursday, April 04, 2013

Multiple submit event handlers on Ajax forms in a wizard style application

.. this was driving me crazy!

I was working on an application (ASP.NET, MVC3) in which I had to implement a wizard like piece of functioality. You know of the 'Next-Next-Previous-Next-Finish'-type.
To make things nice for the user I used AJAX. To make things simpler for me I have one control that is loaded and depending on the page that the user is currently on I load different controls into the form. I return a PartialView and that replaces part
That is all not too complicated and works like a charm.
Uhm, no it doesn't
The first page shows up. I hit the submit button and the page went by smoothly and I could see the ControllerAction being hit. Great! I just increased the page counter and onwards to the next page. The next page arrives in the browser and I hit the submit button again.
Uh oh! Things go wrong now. My ControllerAction is hit twice! Worse even on the next page, it gets hit three times! Oh my, oh my.
Inspection of the problem
Looking at the html closer I quickly discovered that the form had multiple event handlers tied to the 'submit' event. This was the cause of the forms being submitted multiple times.
How come these multiple event handlers then?
Apparently the jQuery Ajax (jquery.unobtrusive-ajax.min.js) has some trouble with the Ajax replacing a part of the DOM and then reattaches some of the existing 'submit' Event handlers. The extra event handler on each page is thus explained.
Root cause is that the AJAX call reloads part of the DOM including the FORM-element and not the content of the FORM and thus somehow the old submit eventhandlers are not cleared. Hence, multiple event handlers.
Okay, so let me Google that for you
Or Bing it. Or Yahoo it.
Many people have encountered similar problems. But none quite the same as mine or at least the provided solutions did not solve my problem. And so I experimented with many solutions and all of them failed.
Thought about somehow preventing all the event handlers but the first to fire. Or just to delete all event handlers except the first. Couldn't find a good way of doing that.

Back to the drawing board
Completely rebuilding the Views and PartialViews to refresh the DOM inside the FORM element was not an option. So that was ruled out.
In the end we decided to go a slightly other way. We decided to step away from a normal submit button as that triggers (all) the submit event handler(s) of the FORM and makes us lose control of the mess we got in. We created a simple button that calls a JavaScript function that does the submit of the FORM through jQuery AJAX. This way we never ever call the submit event handler of the form.
Problem solved.
Please find below the simple JavaScript function that does all the clever stuff.
function onSingleSubmitButtonClicked(button)
{
  var frm = $('form');
  var data =  frm.serialize();
  var url = frm.attr('action');
  var method = frm.attr('data-ajax-method');
  var target = frm.attr('data-ajax-update');
  $.ajax({
    type: method,
    url: url,
    data: data,
    beforeSend: function() {showLoader();},
    complete: function(response) {$(target).html(response.responseText);hideLoader();}
  });
}
We attach this function to the click event of input[type=button] and that is all. Below a step by step run through.
  1. The function finds the one form on our page. When you have more forms on a page you may have to pass an id or when the button is inside the form find the parent of the button of type FORM.
  2. Next we serialize all the input fields from the form.
  3. We then grab the action attribute from the form and use that as the url to post the data to.
  4. We read the post or get method from the appropriate method.
  5. The update target where we will put the response from the Ajax call into the DOM.
  6. Then we make the Ajax call. The serialized data is included and we also show a loader overlay before we send the request and hide it again after the call is complete.
Probably some optimization is possible, but that is a simple solution to the weird problem. Just take things in your own hands. You can't always trust the browser or jQuery do things the way you would expect them to do.

Thursday, June 16, 2011

Using simple jQuery to detect changes

.. a quick solution

Updated
Today I had a small task assigned to me to prevent a user from navigating away from a web form when there are unsaved changes. As it was a rather extensive form (and old code) I had the feeling that it could be quite an extensive task.
Yes, I know this is more of a beginner's level blogpost, but maybe someone can use it. Otherwise burn me down to the ground in the comments.
General idea
A simple solution came to mind:
  1. do this on the client
  2. in comes JavaScript and yep, here comes jQuery!
  3. have a page wide boolean isDirty that starts as 'false'
  4. when an input control is changed, make isDirty 'true'
  5. when user tries to leave the page, check the value of isDirty
  6. when isDirty is 'true' show confirmation dialog
  7. if the user cancels stay on the page
  8. if the user ignores then proceed onwards
All rather simple.
Let's implement this!
Steps 1 and 2 are easy to take. We will just include the jQuery library in the page and start a block of JavaScript.
Also simple is step 3.

var isDirty = false;

Step 6,7 and 8 can be wrapped in a simple function:
function IsPageDirty() {
   if (isDirty) {
      var ignoreChanges = confirm("Unsaved changes. Proceed anyway?");
         return (ignoreChanges);
   }
   return true;
}

That was easy, as some would say.
Now the final leaps
Then I decided that it would be simple a matter of using a selector in jQuery and add a function to the change function that sets the isDirty boolean to 'true'.
I opted for a class 'trackChanges'.
This is the little snippet that I produced.
$(".trackchange").change(function() {
   isDirty = true;
});

Similarly I chose a class of 'moveaway' for those links that can lead the user away from the current page. These links should get a onclick attribute returning the the result from the IsPageDirty() function described above.
$(".moveaway").attr("onclick", "return IsPageDirty();");
And then there was a lot of work ...
Since I then had to go through the entire form. It was an aspx page and there User Controls (ascx) being loaded and I had to go through thse as well. Not very pleasant.
I got myself another cup of coffee and had a good thought. Then I decided to just select all input controls and not just certain marked with a class.
Changed the code to the following.
$("input, select").change(function() {
   isDirty = true;
});
Same was true for all the links. I opted for selecting all anchors.

$("a").attr("onclick", "return IsPageDirty();");

Then I had a huge problem, because the saving was also done using a link. Before the saving would start the user gets a warning that there are unsaved changes. A bit confusing to say the least.
I didn't want to go back to putting a class on all the other links just to get around this. I wanted filter out some of the anchors by putting a class on these.
Luckily, jQuery has a function .not() that takes a selector and filters out these from the selection it is applied to. So I put a class 'always' on the anchors that should not have the IsPageDirty() check and changed the line to:

$("a").not(".always").attr("onclick", "return IsPageDirty();");
And that was it! A very simple piece of JavaScript code and hardly any changes to the page itself. I only had to add a class to some of the links.
Bringing it all together
Even if it is a very simple scenario and the code is probably not as compact as it can be, I think it shows once again how versatile JavaScript is in combination with library like jQuery.
So to get all together

<script type="text/javascript">
   var isDirty = false;

   $(document).ready(function() {
      isDirty = false;
      $("input, select").change(function() {
         isDirty = true;
      });
      $("a").not(".always").attr("onclick", "return IsPageDirty();");
      // a with class "always" is not checked
   });

   function IsPageDirty() {
      if (isDirty) {
         var ignoreChanges = confirm("Unsaved changes. Proceed anyway?");
            return (ignoreChanges);
      }
      return true;
   }
</script>

Not earth shocking
I know, I know, but still I think it could be of some help for others somewhere on the web. And maybe to help me remember this for future reference.

Small update
The above solution worked on almost all of the pages, but one of the pages had some ASP.NET AJAX stuff on it, where dropdownlist were filled after the initial pageload. This caused some nasty problems, because these made the
change()
function go off and set the
isDirty
variable to true.
Some scratching of the head and then some Googling made find the
live()
function to be my saviour in this.

<script type="text/javascript">
   var isDirty = false;

   $(document).ready(function() {
      isDirty = false;
      $("input, select").not(".ignore").live("change", (function() {
         // control with class "ignore" does not fire the isDirty flag
         isDirty = true;
      });
      $("a").not(".always").attr("onclick", "return IsPageDirty();");
      // a with class "always" is not checked
   });

   function IsPageDirty() {
      if (isDirty) {
         var ignoreChanges = confirm("Unsaved changes. Proceed anyway?");
            return (ignoreChanges);
      }
      return true;
   }
</script>

A reader with a good eye also notices the
.not(".ignore")
function I added to exclude some controls from firing the isDirty flag. I used this class on some input controls used for search.

That's it for now!
Enhanced by Zemanta

Friday, May 07, 2010

On jQuery templating

.. isn't it just shifting functionality around?

Eventhough jQuery templating is a pretty interesting concept and I feel tempted to use it, I can not stop thinking that it does not help to simplify my code.

I develop in ASP.NET mainly and do construct all the controls by hand, meaning that I first decide what my HTML will look like and then let the control render exactly that markup.

All right, all right

Arguably, I can do that with jQuery templating equally well. But I will be forced to rebuild the ViewState principle of ASP.NET in order to make changes come back to the server. I must call web services to get the data. Meaning that for each form I must have a specific web service. That is quite a bit of work.

Also I would be doing a lot of coding in JavaScript and in C# and I ike to keep as much functionality as possible in my main development language. Just to avoid confusion.

Concluding

Then again, I may have missed some stuff and maybe things can be done that I do not see yet. It's still early in the jQuery templating model and things are bound to change. I will wait and see what comes up next in this area.
For now, I will stick with my MVC inspired, Mediator based framework.

In reference to: A few thoughts on jQuery templating with jQuery.tmpl | Encosia (view on Google Sidewiki)

Reblog this post [with Zemanta]