17
Jan '19

In my latest projects I’ve found myself increasingly putting input forms within bootstrap modals. It cleanly separates active and static elements, it maintains a tidy UI, and it reduces page load time to name but a few benefits.

A key drawback however is that it’s very easy to click away from a modal by accident and lose your work. To deal with this I created a quick script that listens for changes in any input elements within a modal, and catches the modal close event accordingly:

var is_clicked = false;
var dirty = false;

$().ready(function() {

    /*
     * Keeps the change listener inactive until at least one element has been clicked on 
     */
    $('.modal').on('click', 'input, select, textarea', function(e) {
        is_clicked = true;
    });

    /*
     * input element change listener
     */
    $('.modal').on('change', 'input, select, textarea', function(e) {

        if(is_clicked) {
            dirty = true;
        }
    });

    /*
     * Handles the modal hide event
     */
    $('.modal').on('hide.bs.modal', function(e) {
        //Prevent date picker submits from trying to close parent modals
        if($(e.target).hasClass('date-box'))
            return false;

        if(dirty) {
            if(confirm('You have unsaved changes. Are you sure you wish to close this dialog?')) {
                is_clicked = false;
                dirty = false;
                return true;
            }

            return false;
        }

        return true;
    });
});

In reality I tend to use pnotify for my confirmation dialogs, but to keep this example as simple as possible I put in the native javascript call.

01
Nov '18

Ok many of you may already know this one, but it’s a relatively new one to me, and since non-HTML5 browsers are now mostly gone the way of the dinosaur, I’m much more comfortable relying on its elements for my core functionality. If you are still on IE9 or below then you deserve all you get!

This is as simple as I can make an AJAX form submission handler, that will also deal with files. It uses the HTML5 FormData object to serialise all the form data, including files, and allow us to pass it with the AJAX POST request.

$('.my-form').on('submit', function(e) {

    var form_data = new FormData($(this)[0]);

    $.ajax({
        type: 'POST',
        url: $(this).attr('action'),
        data: form_data,
        dataType: 'json',
        cache: false,
        contentType: false,
        processData: false,
        success: function(response) {
            // Wahey!
        },
        error: function() {
            // Oh dear
        }
    });

    return false;
});

The ‘cache’, ‘contentType’ and ‘processData’ flags ARE necessary to make this work properly. The ‘dataType’ is optional, but if your server doesn’t send back JSON in response to an AJAX request as standard then you need to have a word with yourself!

Also notice that the FormData object needs the DOM version of the form, not the jQuery object, hence the need for “$(this)[0]” in the constructor.

Job. Done.

27
Mar '18

So this one had me puzzled for rather a while. We all know about the bootstrap event ‘shown.bs.modal’ that fires when a modal has been loaded. However, when the modal content is loaded via AJAX, I’ve found that can still be triggered a little bit in advance of contained elements loading in the DOM.

To give a concrete example, I encountered this when trying to activate datatables on a table that I was embedding in a modal. My script was set up as follows:

$('.modal').on('shown.bs.modal', function (e) {
    $('.table-class').DataTable({
        //Datatable options
    });
});

This tended to work about 50% of the time, and after a lot of thought I realised that the times it didn’t run were due to the table element not being loaded into the DOM before the DataTable call is made. Even though the Bootstrap event had been fired, the DOM was yet to catch up.

Now there are various ways around this, most obviously by using ‘setTimeout’ to add a short delay before triggering subsequent logic, although this technique has always struck me as rather imprecise and risky. Fortunately, jQuery provides the ‘ready’ function which gives us all we need to make this logic work perfectly:

$('.modal').on('shown.bs.modal', function (e) {
    $('.table-class').ready(function() {
        $('.table-class').DataTable({
            //Datatable options
        });
    });
});

All we are doing here is adding a subsequent observer to the element so that we can be sure the DOM is aware of it before we try to run any further logic. Simple as that!

21
Oct '17

Cloning simple markup is usually a fairly trivial task in jQuery, using the .clone() function, however it becomes a major headache when there are form elements involved. jQuery’s clone function effectively works based on the raw HTML of the page and as such any changes that have since taken place within the DOM are ignored. This doesn’t sound like such a big deal until you realise that includes changes to all form input elements; the clone will have these all reset to their initial values.

I’ve written a little function to get round this issue, which simply ‘fixes’ the current values fo all input fields (except files) in the markup, allowing them to be cloned correctly:

function fixValues(element) {
    element.find('input[type=text]').each(function() {
        $(this).attr('value', $(this).val());
    });

    element.find('input[type=checkbox], input[type=radio]').each(function() {
        $(this).attr('checked', $(this).is(':checked') ? 'checked' : '');
    });

    element.find('select option:selected').each(function() {
        $(this).attr('selected', 'selected');
    });

    element.find('textarea').each(function() {
        $(this).html($(this).val());
    });
}

Just trigger this function on the element you want to clone, immediately before calling the jQuery clone function (or, if you’re feeling really enthusiastic, you could even extend the clone function itself) and your values should all be copied over as expected.

29
May '17

Like me, you may find the jQuery $.getJSON function really useful for quick GET-based AJAX calls, and lament the lack of a corresponding POST function. Well here it is:

$.postJSON = function(url, data, func) { $.post(url, data, func, 'json'); }

You’re welcome!

01
Sep '16

Quick one here, but one that it took me a while to discover so I thought I’d share it. If you want to have your script change the current address without actually navigating anywhere, you can push to the browser history using the following command:

History.pushState({}, title, url);

The latest item in the history will always display as the current URL in the address bar.

I find this particularly useful when I have a lot of dynamic elements on a page, particularly tabs and accordions.
For example if I wish to retain an opened tab across a page refresh I can use this trick to change the window location hash then use a simple script like this to switch to that tab on page load:

if (window.location.hash) {
    $("a[href='" + window.location.hash + "']").tab('show');
}
04
Jun '15

This all related to a bit of a quirk of the way javascript operates on click events. You would think that in order to triggering analytics on a download link you could just add a click handler to the link and it would be all good. Unfortunately this is not the case. As analytics triggers are pretty much universally ajax calls the script will continue before it gets a response, and if that continuation leads to a download (or indeed simply to navigation away from the current page) then the analytics trigger will fail. My solution involves forcing the browser to give analytics a couple of seconds to return before completing:

$(document).on('click', 'a.download-file', function() {
    var element = $(this);

    ga('send', 'event', 'File', 'Download', 'File Download');

    setTimeout(function() {
        window.location.href = element.attr('href');
    }, 2000);

    return false;
});

Of course it’s not bullet-proof but it’s my go-to solution for these situations.