Avoid double submissions on HTML forms

Some users still live in era of Windows 95 where you had to do double-click to make something happen. And this double-clicking goes with them when they go online. So they double-click on links, double click on submit buttons… Wait? did you just double-clicked that button? submitted the form twice? OOPS!

There are a lot of advice online how to stop this happening, but I have not found a complete solution. Here is my stub at this (using jQuery):

$(':submit').click(function () {
    var $button = this;
    var oldValue = $button.value;
    var oldHtml = $button.innerHTML;
    setTimeout(function () {
        $button.disabled = true;
        if (oldValue) {
            $button.value = 'One moment...';
        }
        if (oldHtml) {
            $button.innerHTML = 'One moment...';
        }
        setTimeout(function () {
            $button.disabled = false;
            if (oldValue) {
                $button.value = oldValue;
            }
            if (oldHtml) {
                $button.innerHTML = oldHtml;
            }
        }, 3000);
    }, 0);
});

What are we doing here? seems like too much code for a simple task. A lot of advice goes to e.preventDefault() but that stops the form submission. Some people recommend adding form.submit() after the preventing default action. That smells. What if I did not want to submit the form, but some other event was triggered on click? If you just disable the button on click – your form will not be submitted because disabled html elements are not submitted.

In this snippet I’m delaying button disabling. This gives a chance to other events to fire – I don’t need to submit the form manually. And inside that timeout I’m setting another delayed function to re-activate the function. Because in era where JavaScript is executed on servers (node.js, I’m looking at you!), it is just plain wrong not to do a validation on a client. So in case when the validation has failed, I re-enable the button again so user can re-submit.

I’ve tried that in latest (17 Dec 2014) Chrome and in IE9 in both cases works as expected. Let me know how this can be improved!

UPDATE 29 Feb 2016

Added support for button html elements along with existing input elements. Made the code quite ugly, but we don’t want to set innerHTML for input element because it should not have one.

Load jQuery Accordion from AJAX call

Today I needed to load a massive amount of text into dropped-down section on the page. And it was unlikely that users will expand that section, so no point in loading a huge load of data until it is needed. That is AJAX is for, isn’t it?

Here is the HTML for the Accordion:

<div class="accordion-ajax" data-url='/path/to/get/text/from'>
    <div class='accordion-head'>
       This is visible text to be presented for clicking
    </div>
    <div><!--This is required: it will be filled in with body of accordion when it is expanded--></div>
</div>

And here is the JavaScript code:

    $('.accordion-ajax').accordion({
        collapsible: true,
        active: false,
        clearStyle: true,
        autoHeight: false,
        header: '.accordion-head',          // point to the class that is used as a header
        heightStyle: 'content',
        icons: false,
        beforeActivate: function (event, ui) {
            var self = $(this); 

            // this bit is tricky. Make sure you have no empty space in the body of drop-down 
            if (ui.newPanel.html() == '') {         
                // taking data-url parameter from accordion header div
                // and load the returned contents into the accordion body.
                // presuming HTML is returned. 
                ui.newPanel.load(self.data('url'));
            }
        }
    }); 

See more details on API in jQuery Accordion API

jQuery Unobtrusive Validation of dates in Chrome: US vs GB format

UPDATE 31 Jan 2013: We have encountered this issue after we have updated our MVC3 project to MVC5. A year ago we have moved MVC3 to MVC4. And suddenly Chrome started to insist on incorrect date format, but everywhere else we have set Globalise locale to be en-Gb. The only other alternative was to disable date validations which can also work – I trust server side validation more than I trust JavaScript ;-)


Today I came across the magical problem in Chrome – it ignores locale information for date format. So it always uses US format: “mm/dd/yyyy”. But largest part of the world is not using this messed up month-first approach (wink-wink).

Continue reading

ASP.Net MVC: Autosave drop-down with notification

I did spend quite a time figuring out jQuery verbs to do just what I needed. So I’ll share this with my blog and possible readers. On my view I had a model written out, some other stuff, but here I’m only interested in DropDown and JavaScript bits:

@Html.DropDownListFor(m => model.tagId, new SelectList(ViewBag.Tags, "Value", "Text", model.tagId), new {@class = "document-category", data_id = model.modelId})

where

ViewBag.Tags was populated in controller like this:

ViewBag.Tags = _tagRepository.All
    .OrderBy(t => t.Name)
    .Select(t => new { Text = t.Name, Value = t.TagId.ToString() })
    .OrderBy(t => t.Text)
    .ToList();

Back to the view. Added JavaScript like this one:

$(document).on("change", ".document-category", function () {
    var select = $(this);
    $.ajax({
        type: "POST",
        url: "@(Url.Action("Action", "Controller"))",
        data: { id: select.data("id"), 
        tagId: $("option:selected", select).val()},
        error: function () {
            select.after("Error occurred");
        },
        success: function (data) {
            if (data.Success === true) {
                select.after(function () {
                    return $('<div> Saved </div>').delay(1000).fadeOut(1000);
                });
            } else {
                select.after("Error occurred");
            }
        }
    });
});

The very first line can be replaced by $(".document-category").change(function(){ but this time I had to deal with other java-script library that was stripping-bare my stuff. On the other end of the ajax request you need to put controller action:

[HttpPost]
public virtual JsonResult Action(int id, int? tagId)
{
        //blah, do some update stuff
    return Json(new {Success = true, Message = "Updated"});
}